home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2324 / 2324.xpi / chrome / sessionmanager.jar / content / sessionmanager / sessionmanager.js < prev    next >
Encoding:
JavaScript  |  2009-11-21  |  156.1 KB  |  4,091 lines

  1. // To Do:
  2. // 1. On crash if don't select to restore current session or select tabs, window sessions will be lost.  Should label window sessions as such in 
  3. //    windows list and restore said sessions if don't select tabs for that window.  Might be tricky.
  4. // 2. Add way of combining delete/load/save/etc into existing window prompt and letting user choose to perform functionality without
  5. //    having the prompt window close. (Session Editor)
  6. // 3. Add sub-grouping
  7. // 4. Add support for hot keys for saving and restoring
  8. // 5. Firefox's tab close confirmation will display when closing the last browser window in Firefox 3.0 and 3.5.  This will result in two 
  9. //    in a row for OS X since closing the last window triggers "shutdown".  Firefox 3.6 puts up the Quit prompt in this instance, which
  10. //    Session Manager now prevents from occuring.  Might want to "fix" FF 3.0 and 3.5 by checking for DOMWindowClosed in the component.
  11.  
  12. var gSessionManager = {
  13.     _timer : null,
  14.     _win_timer : null,
  15.     _clear_state_timer: null,
  16.     
  17.     // Browser Components
  18.     mObserverService: Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService),
  19.     mPrefRoot: Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch2),
  20.     mWindowMediator: Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator),
  21.     mPromptService: Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService),
  22.     mProfileDirectory: Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsILocalFile),
  23.     mIOService: Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService),
  24.     mSecretDecoderRing: Components.classes["@mozilla.org/security/sdr;1"].getService(Components.interfaces.nsISecretDecoderRing),
  25.     mNativeJSON: Components.classes["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON),
  26.     mSMHelper: Components.classes["@morac/sessionmanager-helper;1"].getService(Components.interfaces.nsISessionManangerHelperComponent),
  27.     mVersionCompare: Components.classes["@mozilla.org/xpcom/version-comparator;1"].getService(Components.interfaces.nsIVersionComparator),
  28.     mComponents: Components,
  29.     
  30.     // conditional Browser Components (may or may not exist)
  31.     mPrivateBrowsing: null,
  32.     mSessionStore : null,
  33.     mSessionStartup : null,
  34.     mApplication: null,
  35.  
  36.     mObserving: ["sessionmanager:windowtabopenclose", "sessionmanager:updatetitlebar", "sessionmanager:initial-windows-restored",
  37.                  "sessionmanager:close-windowsession", "browser:purge-session-history", "quit-application-requested", "quit-application-granted"],
  38.     // These won't be removed on last window closed since we still need to watch for them.
  39.     mObserving2: ["quit-application", "private-browsing-change-granted", "sessionmanager:process-closed-window"],
  40.     mClosedWindowFile: "sessionmanager.dat",
  41.     mBackupSessionName: "backup.session",
  42.     mBackupSessionRegEx: /^backup(-[1-9](\d)*)?\.session$/,
  43.     mAutoSaveSessionName: "autosave.session",
  44.     mSessionExt: ".session",
  45.     mFirstUrl: "http://sessionmanager.mozdev.org/documentation.html",
  46.     mSessionRegExp: /^\[SessionManager v2\]\nname=(.*)\ntimestamp=(\d+)\nautosave=(false|session\/?\d*|window\/?\d*)\tcount=([1-9][0-9]*)\/([1-9][0-9]*)(\tgroup=([^\t|^\n|^\r]+))?(\tscreensize=(\d+)x(\d+))?/m,
  47.  
  48.     mClosingWindowState: null,
  49.     mCleanBrowser: null,
  50.     mClosedWindowName: null,
  51.     
  52.     // Application storage names
  53.     mSessionCache: "sessionmanager.cache.session.",
  54.     mClosedWindowsCacheData: "sessionmanager.cache.closedWindows.data",
  55.     mClosedWindowsCacheTimestamp: "sessionmanager.cache.closedWindows.timestamp",
  56.     mClosedWindowsCacheLength: "sessionmanager.cache.closedWindows.length",
  57.     mActiveWindowSessions: "sessionmanager.activeWindowSessions",
  58.     mAlreadyShutdown: "sessionmanager.alreadyShutdown",
  59.     mShutdownPromptResults: "sessionmanager.shutdown_prompt_results",
  60.     
  61.     // Preferences
  62.     mSanitizePreference: "privacy.item.extensions-sessionmanager",
  63.     
  64.     // Mozilla Branch Version
  65.     mPlatformVersion: 0,
  66.     
  67.     getSessionStoreComponent : function() {
  68.         // Firefox or SeaMonkey
  69.         var sessionStore = Components.classes["@mozilla.org/browser/sessionstore;1"] || Components.classes["@mozilla.org/suite/sessionstore;1"];
  70.         var sessionStart = Components.classes["@mozilla.org/browser/sessionstartup;1"] || Components.classes["@mozilla.org/suite/sessionstartup;1"];
  71.         
  72.         if (sessionStore && sessionStart) {
  73.             this.mSessionStore = sessionStore.getService(Components.interfaces.nsISessionStore);
  74.             this.mSessionStartup = sessionStart.getService(Components.interfaces.nsISessionStartup);
  75.         }
  76.         // Not supported
  77.         else {
  78.             window.addEventListener("load", gSessionManager.onLoad_Uninstall, false);
  79.             return false;
  80.         }
  81.         return true;
  82.     },
  83.     
  84.     initialize: function()
  85.     {
  86.         // import logger function into gSessionManager
  87.         Components.utils.import("resource://sessionmanager/modules/logger.js", this);
  88.     
  89.         // Define Constants using closure functions
  90.         const STARTUP_PROMPT = -11;
  91.         const STARTUP_LOAD = -12;
  92.     
  93.         this.STARTUP_PROMPT = function() { return STARTUP_PROMPT; }
  94.         this.STARTUP_LOAD = function() { return STARTUP_LOAD; }
  95.         
  96.         // Get SessionStore service component 
  97.         if (!this.getSessionStoreComponent()) return false;
  98.         
  99.         // Get FUEL (SMILE in SeaMonkey) library
  100.         if (Components.classes["@mozilla.org/fuel/application;1"]) {
  101.             this.mApplication = Components.classes["@mozilla.org/fuel/application;1"].getService(Components.interfaces.fuelIApplication);
  102.         } else if (Components.classes["@mozilla.org/smile/application;1"]) {
  103.             this.mApplication = Components.classes["@mozilla.org/smile/application;1"].getService(Components.interfaces.smileIApplication);
  104.         }
  105.         if (!this.mApplication) return false;
  106.         
  107.         // Set Private Browser service variable
  108.         var privateBrowsing = Components.classes["@mozilla.org/privatebrowsing;1"];
  109.         if (privateBrowsing)
  110.             this.mPrivateBrowsing = privateBrowsing.getService(Components.interfaces.nsIPrivateBrowsingService);
  111.  
  112.         // Determine Mozilla version to see what is supported
  113.         try {
  114.             var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
  115.             this.mPlatformVersion = appInfo.platformVersion;
  116.         } catch (e) { dump(e + "\n"); }
  117.             
  118.         // If the shutdown on last window closed preference is not set, set it based on the O/S.
  119.         // Enable for Macs, disable for everything else
  120.         if (!this.mPrefRoot.prefHasUserValue("extensions.sessionmanager.shutdown_on_last_window_close")) {
  121.             if (/mac/i.test(navigator.platform)) {
  122.                 this.setPref("extensions.sessionmanager.shutdown_on_last_window_close", true, true);
  123.             }
  124.             else {
  125.                 this.setPref("extensions.sessionmanager.shutdown_on_last_window_close", false, true);
  126.             }
  127.         }
  128.         
  129.         return true;
  130.     },
  131.  
  132. /* ........ Listeners / Observers.............. */
  133.  
  134.     onLoad_proxy: function()
  135.     {
  136.         this.removeEventListener("load", gSessionManager.onLoad_proxy, false);
  137.         
  138.         // The close event fires when the window is either manually closed or when the window.close() function is called.  It does not fire on shutdown or when
  139.         // windows close from loading sessions.  The unload event fires any time the window is closed, but fires too late to use SessionStore's setWindowValue.
  140.         // We need to listen to both of them so that the window session window value can be cleared when the window is closed manually.
  141.         // The window value is also cleared on a "quit-application-granted", but that doesn't fire when the last browser window is manually closed.
  142.         window.addEventListener("close", gSessionManager.onClose_proxy, false);            
  143.         window.addEventListener("unload", gSessionManager.onUnload_proxy, false);            
  144.         gSessionManager.onLoad();
  145.     },
  146.  
  147.     onLoad: function(aDialog)
  148.     {
  149.         this.log("onLoad start, aDialog = " + aDialog, "TRACE");
  150.         
  151.         this.mEOL = this.getEOL();
  152.         this.mBundle = document.getElementById("bundle_sessionmanager");
  153.         this.mTitle = this._string("sessionManager");
  154.         
  155.         // Fix tooltips for toolbar buttons
  156.         var buttons = [document.getElementById("sessionmanager-toolbar"), document.getElementById("sessionmanager-undo")];
  157.         for (var i=0; i < buttons.length; i++) {
  158.             if (buttons[i] && buttons[i].boxObject && buttons[i].boxObject.firstChild)
  159.                 buttons[i].boxObject.firstChild.tooltipText = buttons[i].getAttribute("buttontooltiptext");
  160.         }
  161.  
  162.         // This will force SessionStore to be enabled since Session Manager cannot work without SessionStore being 
  163.         // enabled and presumably anyone installing Session Manager actually wants to use it. 
  164.         // This preference no longer exists as of Firefox 3.5 so don't set it.
  165.         if (this.mVersionCompare.compare(this.mPlatformVersion,"1.9.1a1pre") < 0) {
  166.             if (!this.getPref("browser.sessionstore.enabled", true, true)) {
  167.                 this.setPref("browser.sessionstore.enabled", true, true);
  168.             }
  169.         }
  170.         
  171.         this.mPrefBranch = this.mPrefRoot.QueryInterface(Components.interfaces.nsIPrefService).getBranch("extensions.sessionmanager.").QueryInterface(Components.interfaces.nsIPrefBranch2);
  172.         
  173.         // Flag to determine whether or not to use SessionStore Closed Window List (only avaiable in Firefox 3.5 and later)
  174.         this.mUseSSClosedWindowList = (this.getPref("use_SS_closed_window_list", true)) && 
  175.                                       (typeof(this.mSessionStore.getClosedWindowCount) == "function");
  176.                                       
  177.         if (typeof(window.SessionManager) == "undefined") // if Tab Mix Plus isn't installed
  178.         {
  179.             window.SessionManager = gSessionManager;
  180.         }
  181.                 
  182.         if (aDialog || this.mFullyLoaded)
  183.         {
  184.             return;
  185.         }
  186.         
  187.         // This will handle any left over processing that results from closing the last browser window, but
  188.         // not actually exiting the browser and then opening a new browser window.  We do this before adding the observer
  189.         // below because we don't want to run on the opening window, only on the closed window
  190.         if (this.getBrowserWindows().length == 1) this.mObserverService.notifyObservers(window, "sessionmanager:process-closed-window", null);
  191.         
  192.         this.mObserving.forEach(function(aTopic) {
  193.             this.mObserverService.addObserver(this, aTopic, false);
  194.         }, this);
  195.         this.mObserving2.forEach(function(aTopic) {
  196.             this.mObserverService.addObserver(this, aTopic, false);
  197.         }, this);
  198.         
  199.         this.mPref_append_by_default = this.getPref("append_by_default", false);
  200.         this.mPref_autosave_session = this.getPref("autosave_session", true);
  201.         this.mPref_backup_on_restart = this.getPref("backup_on_restart", false);
  202.         this.mPref_backup_session = this.getPref("backup_session", 1);
  203.         this.mPref_click_restore_tab = this.getPref("click_restore_tab", true);
  204.         this.mPref_enable_saving_in_private_browsing_mode = this.getPref("enable_saving_in_private_browsing_mode", false);
  205.         this.mPref_encrypt_sessions = this.getPref("encrypt_sessions", false);
  206.         this.mPref_encrypted_only = this.getPref("encrypted_only", false);
  207.         this.mPref_hide_tools_menu = this.getPref("hide_tools_menu", false);
  208.         this.mPref_max_backup_keep = this.getPref("max_backup_keep", 0);
  209.         this.mPref_max_closed_undo = this.getPref("max_closed_undo", 10);
  210.         this.mPref_max_display = this.getPref("max_display", 20);
  211.         this.mPref_logging = this.getPref("extensions.sessionmanager.logging", false, true);
  212.         this.mPref_name_format = this.getPref("name_format", "%40t-%d");
  213.         this.mPref_overwrite = this.getPref("overwrite", false);
  214.         this.mPref_preselect_previous_session = this.getPref("preselect_previous_session", false);
  215.         this.mPref_reload = this.getPref("reload", false);
  216.         this.mPref_restore_temporary = this.getPref("restore_temporary", false);
  217.         this.mPref_resume_session = this.getPref("resume_session", this.mBackupSessionName);
  218.         this.mPref_save_closed_tabs = this.getPref("save_closed_tabs", 2);
  219.         this.mPref_save_closed_windows = this.getPref("save_closed_windows", 2);
  220.         this.mPref_save_cookies = this.getPref("save_cookies", false);
  221.         this.mPref_save_window_list = this.getPref("save_window_list", false);
  222.         this.mPref_session_list_order = this.getPref("session_list_order", 1);
  223.         this.mPref_session_name_in_titlebar = this.getPref("session_name_in_titlebar", 0);
  224.         this.mPref_shutdown_on_last_window_close = this.getPref("shutdown_on_last_window_close", false);
  225.         this.mPref_startup = this.getPref("startup",0);
  226.         this.mPref_submenus = this.getPref("submenus", false);
  227.         this._temp_restore = this.mApplication.storage.get("sessionmanager.command_line_data", null);
  228.         
  229.         // make sure command line data is cleared
  230.         if (this._temp_restore) this.mApplication.storage.set("sessionmanager.command_line_data", null);
  231.         
  232.         // split out name and group
  233.         this.getAutoSaveValues(this.getPref("_autosave_values", ""));
  234.         this.mPrefBranch.addObserver("", this, false);
  235.         
  236.         gBrowser.addEventListener("TabClose", this.onTabOpenClose, false);
  237.         gBrowser.addEventListener("TabOpen", this.onTabOpenClose, false)
  238.         if (this.mPref_reload) {
  239.             gBrowser.addEventListener("SSTabRestoring", this.onTabRestoring_proxy, false);
  240.             gBrowser.addEventListener("SSTabRestored", this.onTabRestored_proxy, false);
  241.         }
  242.         
  243.         // Make sure resume_session is not null.  This could happen in 0.6.2.  It should no longer occur, but 
  244.         // better safe than sorry.
  245.         if (!this.mPref_resume_session) {
  246.             this.setPref("resume_session", this.mBackupSessionName);
  247.             if (this.mPref_startup == 2) this.setPref("startup",0);
  248.         }
  249.         
  250.         // Hide Session Manager toolbar item if option requested
  251.         this.showHideToolsMenu();
  252.         
  253.         // Undo close tab if middle click on tab bar - only do this if Tab Clicking Options
  254.         // or Tab Mix Plus are not installed.
  255.         this.watchForMiddleMouseClicks();
  256.  
  257.         // Handle restoring sessions do to crash, prompting, pre-chosen session, etc
  258.         this.recoverSession();
  259.         this.updateToolbarButton();
  260.         
  261.         // Tell Session Manager Helper Component that it's okay to restore the browser startup preference if it hasn't done so already
  262.         this.mObserverService.notifyObservers(null, "sessionmanager:restore-startup-preference", null);
  263.         
  264.         // Update other browsers toolbars in case this was a restored window
  265.         if (this.mUseSSClosedWindowList) {
  266.             this.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  267.         }
  268.         
  269.         if (!this.isRunning())
  270.         {
  271.             // make sure that the _running storage value is running
  272.             this.setRunning(true);
  273.         
  274.             // If backup file is temporary, then delete it
  275.             try {
  276.                 if (this.getPref("backup_temporary", true)) {
  277.                     this.setPref("backup_temporary", false)
  278.                     this.delFile(this.getSessionDir(this.mBackupSessionName));
  279.                 }
  280.             } catch (ex) { this.logError(ex); }
  281.  
  282.             // If we did a temporary restore, set it to false            
  283.             if (this.mPref_restore_temporary) this.setPref("restore_temporary", false)
  284.  
  285.             // Force saving the preferences
  286.             this.mObserverService.notifyObservers(null,"sessionmanager-preference-save",null);
  287.         }
  288.         else if (this.getPref("_save_prefs",false)) {
  289.             // Save preference file if this preference is true in order to prevent problems on a crash.
  290.             // It is set to true if an autosave session crashed and user did not resume it.
  291.             this.delPref("_save_prefs");
  292.             this.mObserverService.notifyObservers(null,"sessionmanager-preference-save",null);
  293.         }
  294.         this.mFullyLoaded = true;
  295.         
  296.         // Watch for changes to the titlebar so we can add our sessionname after it since 
  297.         // DOMTitleChanged doesn't fire every time the title changes in the titlebar.
  298.         gBrowser.ownerDocument.watch("title", gSessionManager.updateTitlebar);
  299.         gBrowser.updateTitlebar();
  300.  
  301.         // Workaround for bug 366986
  302.         // TabClose event fires too late to use SetTabValue to save the "image" attribute value and have it be saved by SessionStore
  303.         // so make the image tag persistant so it can be read later from the xultab variable.
  304.         this.mSessionStore.persistTabAttribute("image");
  305.         
  306.         // SeaMonkey doesn't have an undoCloseTab function so create one
  307.         if (typeof(undoCloseTab) == "undefined") {
  308.             undoCloseTab = function(aIndex) { gSessionManager.undoCloseTabSM(aIndex); }
  309.         }
  310.         
  311.         // add call to gSessionManager_Sanitizer (code take from Tab Mix Plus)
  312.         // nsBrowserGlue.js use loadSubScript to load Sanitizer so we need to add this here for the case
  313.         // where the user disabled option to prompt before clearing data 
  314.         var cmd = document.getElementById("Tools:Sanitize");
  315.         if (cmd) cmd.setAttribute("oncommand", "gSessionManager.tryToSanitize();" + cmd.getAttribute("oncommand"));
  316.         
  317.         // Clear current window value setting if shouldn't be set.  Need try catch because first browser window will throw an exception.
  318.         try {
  319.             if (!this.__window_session_name) {
  320.                 // Backup _sm_window_session_values first in case this is actually a restart or crash restore 
  321.                 this._backup_window_sesion_data = this.mSessionStore.getWindowValue(window,"_sm_window_session_values");
  322.                 if (this._backup_window_sesion_data) this.getAutoSaveValues(null, true);
  323.             }
  324.         } catch(ex) {}
  325.         
  326.         // Perform any needed update processing
  327.         var oldVersion = this.getPref("version", "")
  328.         var newVersion = this.mApplication.extensions.get("{1280606b-2510-4fe0-97ef-9b5a22eafe30}").version;
  329.         if (oldVersion != newVersion)
  330.         {
  331.             // Fix the closed window data if it's encrypted
  332.             if ((this.mVersionCompare.compare(oldVersion, "0.6.4.2") < 0) && !this.mUseSSClosedWindowList) {
  333.                 // if encryption enabled
  334.                 if (this.mPref_encrypt_sessions) {
  335.                     var windows = this.getClosedWindows_SM();
  336.                     
  337.                     // if any closed windows
  338.                     if (windows.length) {
  339.                         var encrypt_okay = false;
  340.                         while (!encrypt_okay) {
  341.                             try {
  342.                                 // force a master password prompt so we don't waste time if user cancels it
  343.                                 this.mSecretDecoderRing.encryptString("");
  344.                                 encrypt_okay = true;
  345.                             }
  346.                             catch(ex) {};
  347.                         }
  348.  
  349.                         windows.forEach(function(aWindow) {
  350.                             aWindow.state = this.decrypt(aWindow.state, true, true);
  351.                             aWindow.state = this.decryptEncryptByPreference(aWindow.state);
  352.                         }, this);
  353.                         this.storeClosedWindows_SM(windows);
  354.                     }
  355.                 }
  356.             }
  357.  
  358.             // this isn't used anymore
  359.             if (this.mVersionCompare.compare(oldVersion, "0.6.2.5") < 0) this.delPref("_no_reload");
  360.  
  361.             // Clean out screenX and screenY persist values from localstore.rdf since we don't persist anymore.
  362.             if (this.mVersionCompare.compare(oldVersion, "0.6.2.1") < 0) {
  363.                 var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
  364.                 var ls = Components.classes["@mozilla.org/rdf/datasource;1?name=local-store"].getService(Components.interfaces.nsIRDFDataSource);
  365.                 var rdfNode = RDF.GetResource("chrome://sessionmanager/content/options.xul#sessionmanagerOptions");
  366.                 var arcOut = ls.ArcLabelsOut(rdfNode);
  367.                 while (arcOut.hasMoreElements()) {
  368.                     var aLabel = arcOut.getNext();
  369.                     if (aLabel instanceof Components.interfaces.nsIRDFResource) {
  370.                         var aTarget = ls.GetTarget(rdfNode, aLabel, true);
  371.                         ls.Unassert(rdfNode, aLabel, aTarget);
  372.                     }
  373.                 }
  374.                 ls.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush();
  375.             }
  376.                         
  377.             // Add backup sessions to backup group
  378.             if (this.mVersionCompare.compare(oldVersion, "0.6.2.8") < 0) {
  379.                 var sessions = this.getSessions();
  380.                 sessions.forEach(function(aSession) {
  381.                     if (aSession.backup) {
  382.                         this.group(aSession.fileName, this._string("backup_sessions"));
  383.                     }
  384.                 }, this);
  385.             }
  386.             
  387.             this.setPref("version", newVersion);
  388.             
  389.             // If development version, go to development change page
  390.             if (/\.20[0-9][0-9][0-1][0-9][0-3][0-9]/.test(newVersion)) {
  391.                 this.mFirstUrl = "http://sessionmanager.mozdev.org/changelog.xhtml";
  392.             }
  393.             
  394.             // One time message on update
  395.             if (this.getPref("update_message", true)) {
  396.                 setTimeout(function() {
  397.                     var tBrowser = getBrowser();
  398.                     tBrowser.selectedTab = tBrowser.addTab(gSessionManager.mFirstUrl);
  399.                 },100);
  400.             }
  401.             
  402.         }
  403.         this.log("onLoad end", "TRACE");
  404.     },
  405.  
  406.     // If SessionStore component does not exist hide Session Manager GUI and uninstall
  407.     onLoad_Uninstall: function()
  408.     {
  409.         window.removeEventListener("load", gSessionManager.onLoad_Uninstall, false);
  410.         window.addEventListener("unload", gSessionManager.onUnload_Uninstall, false);
  411.     
  412.         var sessionButton = document.getElementById("sessionmanager-toolbar");
  413.         var undoButton = document.getElementById("sessionmanager-undo");
  414.         var sessionMenu = document.getElementById("sessionmanager-menu");
  415.         if (sessionButton) sessionButton.hidden = true;
  416.         if (undoButton) undoButton.hidden = true;
  417.         if (sessionMenu) sessionMenu.hidden = true;
  418.     
  419.         if (!gSessionManager.getPref("browser.sessionmanager.uninstalled", false, true)) {
  420.             var bundle = document.getElementById("bundle_sessionmanager");
  421.             var title = bundle.getString("sessionManager");
  422.             var text = bundle.getString("not_supported");
  423.             var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
  424.             setTimeout(function() { promptService.alert((bundle)?window:null, title, text); }, 0);
  425.             var liExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager);
  426.             liExtensionManager.uninstallItem("{1280606b-2510-4fe0-97ef-9b5a22eafe30}");
  427.             gSessionManager.setPref("browser.sessionmanager.uninstalled", true, true);
  428.         }
  429.     },
  430.     
  431.     // If uninstalling because of incompatability remove preference
  432.     onUnload_Uninstall: function()
  433.     {
  434.         this.removeEventListener("unload", gSessionManager.onUnload_Uninstall, false);
  435.         
  436.         // last window closing, delete preference
  437.         if (gSessionManager.getBrowserWindows().length == 1) {
  438.             gSessionManager.delPref("browser.sessionmanager.uninstalled", true);
  439.         }
  440.     },
  441.     
  442.     // This fires only when the window is manually closed by using the "X" or via a window.close() call
  443.     onClose_proxy: function(aEvent)
  444.     {
  445.         gSessionManager.log("onClose Fired", "INFO");
  446.         gSessionManager.onWindowCloseRequest();
  447.     },
  448.  
  449.     // This fires any time the window is closed.  It fires too late to use SessionStore's setWindowValue.
  450.     onUnload_proxy: function()
  451.     {
  452.         gSessionManager.log("onUnload Fired", "INFO");
  453.         this.removeEventListener("close", gSessionManager.onClose_proxy, false);
  454.         this.removeEventListener("unload", gSessionManager.onUnload_proxy, false);
  455.         gSessionManager.onUnload();
  456.     },
  457.  
  458.     onUnload: function()
  459.     {
  460.         this.log("onUnload start", "TRACE");
  461.         var allWindows = this.getBrowserWindows();
  462.         var numWindows = allWindows.length;
  463.         this.log("onUnload: numWindows = " + numWindows, "DATA");
  464.         
  465.         this.mObserving.forEach(function(aTopic) {
  466.             this.mObserverService.removeObserver(this, aTopic);
  467.         }, this);
  468.         this.mPrefBranch.removeObserver("", this);
  469.         
  470.         gBrowser.removeEventListener("TabClose", this.onTabOpenClose, false);
  471.         gBrowser.removeEventListener("TabOpen", this.onTabOpenClose, false);
  472.         if (this.mPref_reload) {
  473.             gBrowser.removeEventListener("SSTabRestored", this.onTabRestored_proxy, false);
  474.             gBrowser.removeEventListener("SSTabRestoring", this.onTabRestoring_proxy, false);
  475.         }
  476.         gBrowser.mStrip.removeEventListener("click", this.onTabBarClick, false);
  477.         
  478.         // stop watching for titlebar changes
  479.         gBrowser.ownerDocument.unwatch("title");
  480.         
  481.         // Last window closing will leaks briefly since mObserving2 observers are not removed from it 
  482.         // until after shutdown is run, but since browser is closing anyway, who cares?
  483.         if (numWindows != 0) {
  484.             this.mObserving2.forEach(function(aTopic) {
  485.                 this.mObserverService.removeObserver(this, aTopic);
  486.             }, this);
  487.         }
  488.         
  489.         // Stop Session timer and start another if needed
  490.         if (this._timer) { 
  491.             this.log("onUnload: Session Timer stopped because window closed", "INFO");
  492.             this._timer.cancel();
  493.             this._timer = null;
  494.             if (numWindows != 0) allWindows[0].gSessionManager.checkTimer();
  495.         }
  496.  
  497.         this.onWindowClose();
  498.                         
  499.         // This executes whenever the last browser window is closed (either manually or via shutdown).
  500.         if (this.isRunning() && numWindows == 0)
  501.         {
  502.             this._string_preserve_session = this._string("preserve_session");
  503.             this._string_backup_session = this._string("backup_session");
  504.             this._string_backup_sessions = this._string("backup_sessions");
  505.             this._string_old_backup_session = this._string("old_backup_session");
  506.             this._string_prompt_not_again = this._string("prompt_not_again");
  507.             this._string_encrypt_fail = this._string("encrypt_fail");
  508.             this._string_encrypt_fail2 = this._string("encrypt_fail2");
  509.             this._string_save_and_restore = this._string("save_and_restore");
  510.             this._screen_width = screen.width;
  511.             this._screen_height = screen.height;
  512.             
  513.             this.mTitle += " - " + document.getElementById("bundle_brand").getString("brandFullName");
  514.  
  515.             // This will run the shutdown processing if the preference is set and the last browser window is closed manually
  516.             if (this.mPref_shutdown_on_last_window_close && !this.mPref__stopping) {
  517.                 this.mObserving2.forEach(function(aTopic) {
  518.                     this.mObserverService.removeObserver(this, aTopic);
  519.                 }, this);
  520.                 this.shutDown();
  521.                 // Don't look at the session startup type if a new window is opened without shutting down the browser.
  522.                 Application.storage.set(this.mAlreadyShutdown, true);
  523.             }
  524.         }
  525.         this.mBundle = null;
  526.         this.mFullyLoaded = false;
  527.         this.log("onUnload end", "TRACE");
  528.     },
  529.  
  530.     observe: function(aSubject, aTopic, aData)
  531.     {
  532.         this.log("observe: aTopic = " + aTopic + ", aData = " + aData + ", Subject = " + aSubject, "INFO");
  533.         switch (aTopic)
  534.         {
  535.         case "sessionmanager:close-windowsession":
  536.             // notification will either specify specific window session name or be null for all window sessions
  537.             if (this.__window_session_name && (!aData || (this.__window_session_name == aData))) {
  538.                 var abandon = aSubject.QueryInterface(this.mComponents.interfaces.nsISupportsPRBool).data;
  539.                 this.log((abandon ? "Abandoning" : "Closing") + " window session " + this.__window_session_name);
  540.                 if (abandon) {
  541.                     this.abandonSession(true);
  542.                 }
  543.                 else {
  544.                     this.closeSession(true);
  545.                 }
  546.             }
  547.             break;
  548.         case "sessionmanager:initial-windows-restored":
  549.             // check both the backup and current window value just in case
  550.             var window_values = this._backup_window_sesion_data || this.mSessionStore.getWindowValue(window,"_sm_window_session_values");
  551.             if (window_values) this.getAutoSaveValues(window_values, true);
  552.             this.log("observe: Restore new window done, window session = " + this.__window_session_name, "DATA");
  553.             this._backup_window_sesion_data = null;
  554.             break;
  555.         case "sessionmanager:windowtabopenclose":
  556.             // only update all windows if window state changed.
  557.             if ((aData != "tab") || (window == aSubject)) this.updateToolbarButton();
  558.             break;
  559.         case "sessionmanager:process-closed-window":
  560.             // This will handle any left over processing that results from closing the last browser window, but
  561.             // not actually exiting the browser and then opening a new browser window.  The window will be
  562.             // autosaved or saved into the closed window list depending on if it was an autosave session or not.
  563.             // The observers will then be removed which will result in the window being removed from memory.
  564.             if (window != aSubject) {
  565.                 try { 
  566.                     if (!this.closeSession(false)) this.onWindowClose();
  567.                 }
  568.                 catch(ex) { this.logError(ex); }
  569.                 this.mClosingWindowState = null;
  570.                 this.mCleanBrowser = null;
  571.                 this.mClosedWindowName = null;
  572.                 this.mObserving2.forEach(function(aTopic) {
  573.                     this.mObserverService.removeObserver(this, aTopic);
  574.                 }, this);
  575.                 this.log("observe: done processing closed window", "INFO");
  576.             }
  577.             break;
  578.         case "sessionmanager:updatetitlebar":
  579.             gBrowser.updateTitlebar();
  580.             break;
  581.         case "browser:purge-session-history":
  582.             this.clearUndoData("all");
  583.             break;
  584.         case "private-browsing-change-granted":
  585.             switch(aData) {
  586.             case "enter":
  587.                 // Only do the following once
  588.                 if (!this.doNotDoPrivateProcessing) {
  589.                     // Close current autosave session or make an autosave backup (if not already in private browsing mode)
  590.                     if (!this.closeSession(false,true) && this.mPref_autosave_session) {
  591.                         // If autostart or disabling history via options, make a real backup, otherwise make a temporary backup
  592.                         if (this.isAutoStartPrivateBrowserMode()) {
  593.                             this.backupCurrentSession(true);
  594.                         }
  595.                         else {
  596.                             this.autoSaveCurrentSession(true); 
  597.                         }
  598.                     }
  599.  
  600.                     // Prevent other windows from doing the saving processing
  601.                     this.getBrowserWindows().forEach(function(aWindow) {
  602.                         if (aWindow != window) { 
  603.                             aWindow.gSessionManager.doNotDoPrivateProcessing = true; 
  604.                         }
  605.                     });
  606.                 }
  607.                 break;
  608.             case "exit":
  609.                 this.doNotDoPrivateProcessing = false;
  610.                 // If browser shutting down, set flag
  611.                 aSubject.QueryInterface(this.mComponents.interfaces.nsISupportsPRBool);
  612.                 if (aSubject.data) {
  613.                     if (!this.mPref_enable_saving_in_private_browsing_mode || !this.mPref_encrypt_sessions) {
  614.                         this.mShutDownInPrivateBrowsingMode = true;
  615.                     }
  616.                 }
  617.                 break;
  618.             }
  619.             break;
  620.         case "nsPref:changed":
  621.             this["mPref_" + aData] = this.getPref(aData);
  622.             
  623.             switch (aData)
  624.             {
  625.             case "click_restore_tab":
  626.                 this.watchForMiddleMouseClicks();
  627.                 break;
  628.             case "encrypt_sessions":
  629.                 this.encryptionChange();
  630.                 break;
  631.             case "max_closed_undo":
  632.                 if (!this.mUseSSClosedWindowList) {
  633.                     if (this.mPref_max_closed_undo == 0)
  634.                     {
  635.                         this.clearUndoData("window", true);
  636.                     }
  637.                     else
  638.                     {
  639.                         var closedWindows = this.getClosedWindows_SM();
  640.                         if (closedWindows.length > this.mPref_max_closed_undo)
  641.                         {
  642.                             this.storeClosedWindows_SM(closedWindows.slice(0, this.mPref_max_closed_undo));
  643.                         }
  644.                     }
  645.                 }
  646.                 break;
  647.             case "_autosave_values":
  648.                 // split out name and group
  649.                 this.getAutoSaveValues(this.mPref__autosave_values);
  650.                 this.mPref__autosave_values = null;
  651.                 this.checkTimer();
  652.                 gBrowser.updateTitlebar();
  653.                 break;
  654.             case "hide_tools_menu":
  655.                 this.showHideToolsMenu();
  656.                 break;
  657.             case "reload":
  658.                 if (this.mPref_reload) {
  659.                     gBrowser.addEventListener("SSTabRestoring", this.onTabRestoring_proxy, false);
  660.                     gBrowser.addEventListener("SSTabRestored", this.onTabRestored_proxy, false);
  661.                 }
  662.                 else {
  663.                     gBrowser.removeEventListener("SSTabRestoring", this.onTabRestoring_proxy, false);
  664.                     gBrowser.removeEventListener("SSTabRestored", this.onTabRestored_proxy, false);
  665.                 }
  666.                 break;
  667.             case "use_SS_closed_window_list":
  668.                 // Flag to determine whether or not to use SessionStore Closed Window List
  669.                 this.mUseSSClosedWindowList = (this.mPref_use_SS_closed_window_list && 
  670.                                                typeof(this.mSessionStore.getClosedWindowCount) == "function");
  671.                 this.updateToolbarButton();
  672.                 break;
  673.             case "session_name_in_titlebar":
  674.                 gBrowser.updateTitlebar();
  675.                 break;
  676.             }
  677.             break;
  678.         case "quit-application":
  679.             this.mObserving2.forEach(function(aTopic) {
  680.                 this.mObserverService.removeObserver(this, aTopic);
  681.             }, this);
  682.             // only run shutdown for one window and if not restarting browser (or on restart is user wants)
  683.             if (this.mPref_backup_on_restart || (aData != "restart"))
  684.             {
  685.                 this.shutDown();
  686.             }
  687.             else
  688.             {
  689.                 // Save any active auto-save session, but leave it open.
  690.                 this.closeSession(false, false, true);
  691.             }
  692.             break;
  693.         case "quit-application-requested":
  694.             this._restart_requested = (aData == "restart");
  695.             break;
  696.         case "quit-application-granted":
  697.             // If not restarting or if this window doesn't have a window session open, 
  698.             // hurry and wipe out the window session value before Session Store stops allowing 
  699.             // window values to be updated.
  700.             if (!this._restart_requested || !this.__window_session_name) {
  701.                 this.log("observe: Clearing window session data", "INFO");
  702.                 // this throws if it doesn't exist so try/catch it
  703.                 try { 
  704.                     this.mSessionStore.deleteWindowValue(window, "_sm_window_session_values");
  705.                 }
  706.                 catch(ex) {}
  707.             }
  708.         
  709.             // quit granted so stop listening for closed windows
  710.             this.mPref__stopping = true;
  711.             this._mUserDirectory = this.getUserDir("sessions");
  712.             break;
  713.         // timer periodic call
  714.         case "timer-callback":
  715.             if (aSubject == this._clear_state_timer) {
  716.                 this.log("Timer callback to clear closing window state data", "INFO");
  717.                 this.mClosingWindowState = null;
  718.                 this.mCleanBrowser = null;
  719.                 this.mClosedWindowName = null;
  720.                 this._clear_state_timer = null;
  721.             }
  722.             else {
  723.                 // save auto-save or window session if open, but don't close it
  724.                 this.log("Timer callback for " + ((aSubject == this._win_timer) ? "window" : "session" ) + " timer", "EXTRA");
  725.                 this.closeSession((aSubject == this._win_timer), false, true);
  726.             }
  727.             break;
  728.         }
  729.     },
  730.  
  731.     onTabOpenClose: function(aEvent)
  732.     {
  733.         // Give browser a chance to update count closed tab count.  Only SeaMonkey currently needs this, but it doesn't hurt Firefox.
  734.         setTimeout(function() { gSessionManager.updateToolbarButton(); }, 0);
  735.     },
  736.     
  737.     // This is to try and prevent tabs that are closed during the restore preocess from actually reloading.  
  738.     // It doesn't work all the time, but it's better than nothing.
  739.     onTabRestoring_proxy: function(aEvent) {
  740.         // If tab reloading enabled and not offline
  741.         if (gSessionManager.mPref_reload && !gSessionManager.mIOService.offline) {
  742.  
  743.             var sessionStore = gSessionManager.mSessionStore;
  744.             if (sessionStore.getTabValue(aEvent.originalTarget, "session_manager_reload")) {
  745.                 sessionStore.deleteTabValue(aEvent.originalTarget, "session_manager_reload");
  746.             }
  747.             else if (sessionStore.getTabValue(aEvent.originalTarget, "session_manager_allow_reload")) {
  748.                 sessionStore.deleteTabValue(aEvent.originalTarget, "session_manager_allow_reload");
  749.                 sessionStore.setTabValue(aEvent.originalTarget, "session_manager_reload", true);
  750.             }
  751.         }
  752.     },
  753.  
  754.     onTabRestored_proxy: function(aEvent)
  755.     {
  756.         // If tab reloading enabled and not offline
  757.         if (gSessionManager.mPref_reload && !gSessionManager.mIOService.offline) {
  758.  
  759.             // Restore tabs that are marked restore.
  760.             var sessionStore = gSessionManager.mSessionStore;
  761.             var allowReload = sessionStore.getTabValue(aEvent.originalTarget, "session_manager_reload");
  762.             if (allowReload == "true")
  763.             {
  764.                 var nsIWebNavigation = Components.interfaces.nsIWebNavigation;
  765.                 var browser = this.getBrowserForTab(aEvent.originalTarget);
  766.                 browser.reloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
  767.                 
  768.                 // no longer allow tab to reload
  769.                 try {
  770.                     sessionStore.deleteTabValue(aEvent.originalTarget, "session_manager_reload");
  771.                     sessionStore.deleteTabValue(aEvent.originalTarget, "session_manager_allow_reload");
  772.                 }
  773.                 catch (ex) {}
  774.             }
  775.         }
  776.     },
  777.     
  778.     onTabBarClick: function(aEvent)
  779.     {
  780.         //undo close tab on middle click on tab bar
  781.         if (aEvent.button == 1 && aEvent.target.localName != "tab")
  782.         {
  783.             undoCloseTab();
  784.         }
  785.     },
  786.  
  787.     onToolbarClick: function(aEvent, aButton)
  788.     {
  789.         if (aEvent.button == 1)
  790.         {
  791.             // simulate shift left clicking toolbar button when middle click is used
  792.             var event = document.createEvent("XULCommandEvents");
  793.             event.initCommandEvent("command", false, true, window, 0, false, false, true, false, null);
  794.             aButton.dispatchEvent(event);
  795.         }
  796.         else if (aEvent.button == 2 && aButton.getAttribute("disabled") != "true")
  797.         {
  798.             aButton.open = true;
  799.         }
  800.     },
  801.     
  802.     // This is needed because a window close can be cancelled and we don't want to process such as closing a window
  803.     onWindowCloseRequest: function() {
  804.         this.log("onWindowClosedRequest start", "TRACE");
  805.         
  806.         // Clear any previously closing state data so we get fresh data
  807.         this.mClosingWindowState = null;
  808.         this.mCleanBrowser = null;
  809.         this.mClosedWindowName = null;
  810.         
  811.         try {
  812.             // Store closing state if it will be needed later
  813.             if (this.__window_session_name || !this.mUseSSClosedWindowList || (this.getBrowserWindows().length == 1)) {
  814.                 this.log("onWindowClosedRequest saved closing state", "INFO");
  815.                 this.mClosingWindowState = this.getSessionState(null, true, null, null, null, true); 
  816.                 this.mCleanBrowser = Array.every(gBrowser.browsers, this.isCleanBrowser);
  817.                 this.mClosedWindowName = content.document.title || ((gBrowser.currentURI.spec != "about:blank")?gBrowser.currentURI.spec:this._string("untitled_window"));
  818.                 
  819.                 // Set up a one second timer to clear the saved data in case the window isn't actually closing
  820.                 this._clear_state_timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  821.                 this._clear_state_timer.init(gSessionManager, 1000, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
  822.             }
  823.         }
  824.         catch(ex) { 
  825.             this.logError(ex); 
  826.         }
  827.         this.log("onWindowClosedRequest end", "TRACE");
  828.     },
  829.  
  830.     onWindowClose: function()
  831.     {
  832.         this.log("onWindowClosed start", "TRACE");
  833.         if (this._clear_state_timer) {
  834.             this.log("Canceling clear closing window state timer", "INFO");
  835.             this._clear_state_timer.cancel();
  836.             this._clear_state_timer = null;
  837.         }
  838.         
  839.         // if there is a window session save it (leave it open if browser is restarting)
  840.         if (this.__window_session_name) 
  841.         {
  842.             this.closeSession(true, false, this._restart_requested);
  843.         }
  844.             
  845.         this.log("onWindowClose: running = " + this.isRunning() + ", stopping = " + this.mPref__stopping, "DATA");
  846.         
  847.         var numWindows = this.getBrowserWindows().length;
  848.         this.log("onWindowClose: numWindows = " + numWindows, "DATA");
  849.         
  850.         // only save closed window if running and not shutting down 
  851.         if (this.isRunning() && !this.mPref__stopping)
  852.         {
  853.             // save window in closed window list if not last window
  854.             if (numWindows > 0)
  855.             {
  856.                 if (!this.mUseSSClosedWindowList) {
  857.                     var state = this.getSessionState(null, true, null, null, null, true, null, this.mClosingWindowState);
  858.                     this.appendClosedWindow(state);
  859.                 }
  860.                 this.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  861.             }
  862.         }
  863.         // Clear stored closing state if not the last window
  864.         if (numWindows > 0) {
  865.             this.mClosingWindowState = null; 
  866.             this.mCleanBrowser = null;
  867.             this.mClosedWindowName = null;
  868.         }
  869.         this.log("onWindowClosed end", "TRACE");
  870.     },
  871.     
  872.     // Put current session name in browser titlebar
  873.     // This is a watch function which is called any time the titlebar text changes
  874.     // See https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Object/watch
  875.     updateTitlebar: function(id, oldVal, newVal)
  876.     {
  877.         if (id == "title") {
  878.             // Don't kill browser if something goes wrong
  879.             try {
  880.                 var windowTitleName = (gSessionManager.__window_session_name) ? (gSessionManager._string("window_session") + " " + gSessionManager.__window_session_name) : "";
  881.                 var sessionTitleName = (gSessionManager.mPref__autosave_name) ? (gSessionManager._string("current_session2") + " " + gSessionManager.mPref__autosave_name) : "";
  882.                 var title = ((windowTitleName || sessionTitleName) ? "(" : "") + windowTitleName + ((windowTitleName && sessionTitleName) ? ", " : "") + sessionTitleName + ((windowTitleName || sessionTitleName) ? ")" : "")
  883.                 
  884.                 if (title) {
  885.                     // Add window and browser session titles
  886.                     switch(gSessionManager.mPref_session_name_in_titlebar) {
  887.                         case 0:
  888.                             newVal = newVal + " - " + title;
  889.                             break;
  890.                         case 1:
  891.                             newVal = title + " - " + newVal;
  892.                             break;
  893.                     }
  894.                 }
  895.             } 
  896.             catch (ex) { 
  897.                 gSessionManager.logError(ex); 
  898.             }
  899.         }
  900.         return newVal;
  901.     },
  902.     
  903.     // Undo close tab if middle click on tab bar if enabled by user - only do this if Tab Clicking Options
  904.     // or Tab Mix Plus are not installed.
  905.     watchForMiddleMouseClicks: function() 
  906.     {
  907.         if (this.mPref_click_restore_tab && (typeof(tabClicking) == "undefined") && (typeof(TM_checkClick) == "undefined")) {
  908.             gBrowser.mStrip.addEventListener("click", this.onTabBarClick, false);
  909.         }
  910.         else gBrowser.mStrip.removeEventListener("click", this.onTabBarClick, false);
  911.     },
  912.  
  913. /* ........ Menu Event Handlers .............. */
  914.  
  915.     init: function(aPopup, aIsToolbar)
  916.     {
  917.         function get_(a_id) { return aPopup.getElementsByAttribute("_id", a_id)[0] || null; }
  918.         
  919.         var separator = get_("separator");
  920.         var backupSep = get_("backup-separator");
  921.         var startSep = get_("start-separator");
  922.         var closer = get_("closer");
  923.         var closerWindow = get_("closer_window");
  924.         var abandon = get_("abandon");
  925.         var abandonWindow = get_("abandon_window");
  926.         var backupMenu = get_("backup-menu");
  927.                 
  928.         for (var item = startSep.nextSibling; item != separator; item = startSep.nextSibling)
  929.         {
  930.             aPopup.removeChild(item);
  931.         }
  932.         
  933.         // The first time this function is run after an item is added or removed from the browser toolbar
  934.         // using the customize feature, the backupMenu.menupopup value is not defined.  This happens once for
  935.         // each menu (tools menu and toolbar button).  Using the backupMenu.firstChild will work around this
  936.         // Firefox bug, even though it technically isn't needed.
  937.         var backupPopup = backupMenu.menupopup || backupMenu.firstChild; 
  938.         while (backupPopup.childNodes.length) backupPopup.removeChild(backupPopup.childNodes[0]);
  939.         
  940.         closer.hidden = abandon.hidden = (this.mPref__autosave_name=="");
  941.         closerWindow.hidden = abandonWindow.hidden = !this.__window_session_name;
  942.         
  943.         get_("autosave-separator").hidden = closer.hidden && closerWindow.hidden && abandon.hidden && abandonWindow.hidden;
  944.         
  945.         // Disable saving in privacy mode
  946.         var inPrivateBrowsing = this.isPrivateBrowserMode();
  947.         this.setDisabled(get_("save"), inPrivateBrowsing);
  948.         this.setDisabled(get_("saveWin"), inPrivateBrowsing);
  949.         
  950.         var windowSessions = this.getWindowSessions();
  951.         var sessions = this.getSessions();
  952.         var groupNames = [];
  953.         var groupMenus = {};
  954.         var count = 0;
  955.         var backupCount = 0;
  956.         var user_latest = false;
  957.         var backup_latest = false;
  958.         sessions.forEach(function(aSession, aIx) {
  959.             if (!aSession.backup && !aSession.group && (this.mPref_max_display >= 0) && (count >= this.mPref_max_display)) return;
  960.     
  961.             var key = (aSession.backup || aSession.group)?"":(++count < 10)?count:(count == 10)?"0":"";
  962.             var menuitem = document.createElement("menuitem");
  963.             menuitem.setAttribute("label", ((key)?key + ") ":"") + aSession.name + "   (" + aSession.windows + "/" + aSession.tabs + ")");
  964.             menuitem.setAttribute("tooltiptext", menuitem.getAttribute("label"));
  965.             menuitem.setAttribute("oncommand", 'gSessionManager.load("' + aSession.fileName + '", (event.shiftKey && (event.ctrlKey || event.metaKey))?"overwrite":(event.shiftKey)?"newwindow":(event.ctrlKey || event.metaKey)?"append":"");');
  966.             menuitem.setAttribute("onclick", 'if (event.button == 1) gSessionManager.load("' + aSession.fileName + '", "newwindow");');
  967.             menuitem.setAttribute("contextmenu", "sessionmanager-ContextMenu");
  968.             menuitem.setAttribute("filename", aSession.fileName);
  969.             menuitem.setAttribute("backup-item", aSession.backup);
  970.             menuitem.setAttribute("accesskey", key);
  971.             menuitem.setAttribute("autosave", /^window|session/.exec(aSession.autosave));
  972.             menuitem.setAttribute("disabled", windowSessions[aSession.name.trim().toLowerCase()] || false);
  973.             menuitem.setAttribute("crop", "center");
  974.             // only display one latest (even if two have the same timestamp)
  975.             if (!(aSession.backup?backup_latest:user_latest) &&
  976.                 ((aSession.backup?sessions.latestBackUpTime:sessions.latestTime) == aSession.timestamp)) {
  977.                 menuitem.setAttribute("latest", true);
  978.                 if (aSession.backup) backup_latest = true;
  979.                 else user_latest = true;
  980.             }
  981.             if (aSession.name == this.mPref__autosave_name) menuitem.setAttribute("disabled", true);
  982.             if (aSession.backup) {
  983.                 backupCount++;
  984.                 backupPopup.appendChild(menuitem);
  985.             }
  986.             else {
  987.                 if (aSession.group) {
  988.                     var groupMenu = groupMenus[aSession.group];
  989.                     if (!groupMenu) {
  990.                         groupMenu = document.createElement("menu");
  991.                         groupMenu.setAttribute("_id", aSession.group);
  992.                         groupMenu.setAttribute("label", aSession.group);
  993.                         groupMenu.setAttribute("tooltiptext", aSession.group);
  994.                         groupMenu.setAttribute("accesskey", aSession.group.charAt(0));
  995.                         groupMenu.setAttribute("contextmenu", "sessionmanager-groupContextMenu");
  996.                         var groupPopup = document.createElement("menupopup");
  997.                         groupPopup.setAttribute("onpopupshowing", "event.stopPropagation();");
  998.                         groupMenu.appendChild(groupPopup);
  999.                         
  1000.                         groupNames.push(aSession.group);
  1001.                         groupMenus[aSession.group] = groupMenu;
  1002.                     }
  1003.                     var groupPopup = groupMenu.menupopup || groupMenu.lastChild; 
  1004.                     groupPopup.appendChild(menuitem);
  1005.                 }
  1006.                 else aPopup.insertBefore(menuitem, separator);
  1007.             }
  1008.         }, this);
  1009.         
  1010.         // Display groups in alphabetical order at the top of the list
  1011.         if (groupNames.length) {
  1012.             groupNames.sort(function(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); });
  1013.             var insertBeforeEntry = startSep.nextSibling;
  1014.             
  1015.             groupNames.forEach(function(aGroup, aIx) {
  1016.                 aPopup.insertBefore(groupMenus[aGroup], insertBeforeEntry);
  1017.             },this);
  1018.         }
  1019.         
  1020.         backupSep.hidden = backupMenu.hidden = (backupCount == 0);
  1021.         separator.hidden = (this.mPref_max_display == 0) || ((sessions.length - backupCount) == 0);
  1022.         this.setDisabled(get_("load"), separator.hidden && backupSep.hidden);
  1023.         this.setDisabled(get_("rename"), separator.hidden && backupSep.hidden);
  1024.         this.setDisabled(get_("remove"), separator.hidden && backupSep.hidden);
  1025.         this.setDisabled(get_("group"), separator.hidden && backupSep.hidden);
  1026.         
  1027.         var undoMenu = get_("undo-menu");
  1028.         while (aPopup.lastChild != undoMenu)
  1029.         {
  1030.             aPopup.removeChild(aPopup.lastChild);
  1031.         }
  1032.         
  1033.         var undoDisabled = ((this.getPref("browser.sessionstore.max_tabs_undo", 10, true) == 0) &&
  1034.                             ((!this.mUseSSClosedWindowList && (this.mPref_max_closed_undo == 0)) ||
  1035.                              (this.mUseSSClosedWindowList && this.getPref("browser.sessionstore.max_windows_undo", 10, true) == 0)));
  1036.         var divertedMenu = aIsToolbar && document.getElementById("sessionmanager-undo");
  1037.         var canUndo = !undoDisabled && !divertedMenu && this.initUndo(undoMenu.firstChild);
  1038.         
  1039.         undoMenu.hidden = undoDisabled || divertedMenu || !this.mPref_submenus;
  1040.         undoMenu.previousSibling.hidden = !canUndo && undoMenu.hidden;
  1041.         this.setDisabled(undoMenu, !canUndo);
  1042.         
  1043.         if (!this.mPref_submenus && canUndo)
  1044.         {
  1045.             for (item = undoMenu.firstChild.firstChild; item; item = item.nextSibling)
  1046.             {
  1047.                 aPopup.appendChild(item.cloneNode(true));
  1048.                 
  1049.                 // Event handlers aren't copied so need to set them up again to display status bar text
  1050.                 if (item.getAttribute("statustext")) {
  1051.                     aPopup.lastChild.addEventListener("DOMMenuItemActive", function(event) { this.ownerDocument.getElementById("statusbar-display").setAttribute("label",this.getAttribute("statustext")); }, false);
  1052.                     aPopup.lastChild.addEventListener("DOMMenuItemInactive",  function(event) { this.ownerDocument.getElementById("statusbar-display").setAttribute("label",''); }, false); 
  1053.                 }
  1054.             }
  1055.         }
  1056.         
  1057.         // Bug copies tooltiptext to children so specifically set tooltiptext for all children
  1058.         if (aIsToolbar) {
  1059.             this.fixBug374288(aPopup.parentNode);
  1060.         }
  1061.     },
  1062.  
  1063.     save: function(aName, aFileName, aGroup, aOneWindow)
  1064.     {
  1065.         if (this.isPrivateBrowserMode()) return;
  1066.         aOneWindow = aOneWindow; // && (this.getBrowserWindows().length > 1);
  1067.         
  1068.         var values = { text: this.getFormattedName(content.document.title || "about:blank", new Date()) || (new Date()).toLocaleString(), autoSaveable : true };
  1069.         if (!aName)
  1070.         {
  1071.             if (!this.prompt(this._string("save2_session"), this._string("save_" + ((aOneWindow)?"window":"session") + "_ok"), values, this._string("save_" + ((aOneWindow)?"window":"session")), this._string("save_session_ok2")))
  1072.             {
  1073.                 return;
  1074.             }
  1075.             aName = values.text;
  1076.             aFileName = values.name;
  1077.             aGroup = values.group;
  1078.         }
  1079.         if (aName)
  1080.         {
  1081.             var file = this.getSessionDir(aFileName || this.makeFileName(aName), !aFileName);
  1082.             try
  1083.             {
  1084.                 var oldstate = null, merge = false;
  1085.                 // If appending, get the old state and pass it to getSessionState to merge with the current state
  1086.                 if (values.append && aFileName && file.exists()) {
  1087.                     oldstate = this.readSessionFile(file);
  1088.                     if (oldstate) {
  1089.                         var matchArray = this.mSessionRegExp.exec(oldstate);
  1090.                         if (matchArray) {
  1091.                             oldstate = oldstate.split("\n")[4];
  1092.                             oldstate = this.decrypt(oldstate);
  1093.                             if (oldstate) merge = true;
  1094.                         }
  1095.                     }
  1096.                 }
  1097.                 this.writeFile(file, this.getSessionState(aName, aOneWindow, this.getNoUndoData(), values.autoSave, aGroup, null, values.autoSaveTime, oldstate, merge));
  1098.             }
  1099.             catch (ex)
  1100.             {
  1101.                 this.ioError(ex);
  1102.             }
  1103.  
  1104.             // Combine auto-save values into string
  1105.             var autosaveValues = this.mergeAutoSaveValues(aName, aGroup, values.autoSaveTime);
  1106.             if (!aOneWindow)
  1107.             {
  1108.                 if (values.autoSave)
  1109.                 {
  1110.                     this.setPref("_autosave_values", autosaveValues);
  1111.                 }
  1112.                 else if (this.mPref__autosave_name == aName)
  1113.                 {
  1114.                     // If in auto-save session and user saves on top of it as manual turn off autosave
  1115.                     this.setPref("_autosave_values","");
  1116.                 }
  1117.             }
  1118.             else 
  1119.             {
  1120.                 if (values.autoSave)
  1121.                 {
  1122.                     // Store autosave values into window value and also into window variables
  1123.                     this.getAutoSaveValues(autosaveValues, true);
  1124.                 }
  1125.             }
  1126.         }
  1127.     },
  1128.  
  1129.     saveWindow: function(aName, aFileName, aGroup)
  1130.     {
  1131.         this.save(aName, aFileName, aGroup, true);
  1132.     },
  1133.     
  1134.     // if aOneWindow is true, then close the window session otherwise close the browser session
  1135.     closeSession: function(aOneWindow, aForceSave, aKeepOpen)
  1136.     {
  1137.         this.log("closeSession: " + ((aOneWindow) ? this.__window_session_name : this.mPref__autosave_name) + ", aKeepOpen = " + aKeepOpen, "DATA");
  1138.         var name = (aOneWindow) ? this.__window_session_name : this.mPref__autosave_name;
  1139.         var group = (aOneWindow) ? this.__window_session_group : this.mPref__autosave_group;
  1140.         var time = (aOneWindow) ? this.__window_session_time : this.mPref__autosave_time;
  1141.         if (name)
  1142.         {
  1143.             var file = this.getSessionDir(this.makeFileName(name));
  1144.             try
  1145.             {
  1146.                 // If forcing a save or not in private browsing save auto or window session.  Use stored closing window state if it exists.
  1147.                 if (aForceSave || !this.isPrivateBrowserMode()) this.writeFile(file, this.getSessionState(name, aOneWindow, this.getNoUndoData(), true, group, null, time, this.mClosingWindowState));
  1148.             }
  1149.             catch (ex)
  1150.             {
  1151.                 this.ioError(ex);
  1152.             }
  1153.         
  1154.             if (!aKeepOpen) {
  1155.                 if (!aOneWindow) {
  1156.                     this.setPref("_autosave_values","");
  1157.                 }
  1158.                 else {
  1159.                     this.getAutoSaveValues(null, true);
  1160.                 }
  1161.             }
  1162.             return true;
  1163.         }
  1164.         return false;
  1165.     },
  1166.     
  1167.     abandonSession: function(aOneWindow)
  1168.     {
  1169.         var dontPrompt = { value: false };
  1170.         if (this.getPref("no_abandon_prompt") || this.mPromptService.confirmEx(null, this.mTitle, this._string("abandom_prompt"), this.mPromptService.BUTTON_TITLE_YES * this.mPromptService.BUTTON_POS_0 + this.mPromptService.BUTTON_TITLE_NO * this.mPromptService.BUTTON_POS_1, null, null, null, this._string("prompt_not_again"), dontPrompt) == 0)
  1171.         {
  1172.             if (aOneWindow) {
  1173.                 this.getAutoSaveValues(null, true);
  1174.             }
  1175.             else {
  1176.                 this.setPref("_autosave_values","");
  1177.             }
  1178.             if (dontPrompt.value)
  1179.             {
  1180.                 this.setPref("no_abandon_prompt", true);
  1181.             }
  1182.         }
  1183.     },
  1184.  
  1185.     load: function(aFileName, aMode, aChoseTabs)
  1186.     {
  1187.         this.log("load: aFileName = " + aFileName + ", aMode = " + aMode + ", aChoseTabs = " + aChoseTabs, "DATA");
  1188.         var state, chosenState, window_autosave_values, force_new_window = false, overwrite_window = false;
  1189.  
  1190.         if (!aFileName) {
  1191.             var values = { append_replace: true };
  1192.             aFileName = this.selectSession(this._string("load_session"), this._string("load_session_ok"), values);
  1193.             var file;
  1194.             if (!aFileName || !(file = this.getSessionDir(aFileName)) || !file.exists()) return;
  1195.             aChoseTabs = values.choseTabs;
  1196.             aMode = values.append ? "newwindow" : (values.append_window ? "append" : "overwrite");
  1197.         }
  1198.         if (aChoseTabs) {
  1199.             // Get windows and tabs chosen by user
  1200.             chosenState = this.mSMHelper.mSessionData;
  1201.             this.mSMHelper.setSessionData("");
  1202.             
  1203.             // Get session header data from disk
  1204.             state = this.readSessionFile(this.getSessionDir(aFileName), true);
  1205.         }
  1206.         else state = this.readSessionFile(this.getSessionDir(aFileName));
  1207.         if (!state)
  1208.         {
  1209.             this.ioError();
  1210.             return;
  1211.         }
  1212.  
  1213.         var matchArray = this.mSessionRegExp.exec(state);
  1214.         if (!matchArray)
  1215.         {
  1216.             this.ioError();
  1217.             return;
  1218.         }        
  1219.         
  1220.         // If user somehow managed to load an active Window or Auto Session, ignore it
  1221.         if ((/^window/.test(matchArray[3]) && this.mApplication.storage.get(this.mActiveWindowSessions, {})[matchArray[1].trim().toLowerCase()]) ||
  1222.             (/^session/.test(matchArray[3]) && (this.mPref__autosave_name == matchArray[1])))
  1223.         {
  1224.             this.log("Opened an already active auto or window session: " + matchArray[1], "INFO");
  1225.             return;
  1226.         }
  1227.  
  1228.         // handle case when always want a new window (even if current window is blank) and
  1229.         // want to overwrite the current window, but not the current session
  1230.         switch (aMode) {
  1231.             case "newwindow_always":
  1232.                 force_new_window = true;
  1233.                 aMode = "newwindow";
  1234.                 break;
  1235.             case "overwrite_window":
  1236.                 overwrite_window = true;
  1237.                 aMode = "append";            // Basically an append with overwriting tabs
  1238.                 break;
  1239.         }
  1240.         
  1241.         var sessionWidth = parseInt(matchArray[9]);
  1242.         var sessionHeight = parseInt(matchArray[10]);
  1243.         var xDelta = (!sessionWidth || isNaN(sessionWidth)) ? 1 : (screen.width / sessionWidth);
  1244.         var yDelta = (!sessionHeight || isNaN(sessionHeight)) ? 1 : (screen.height / sessionHeight);
  1245.         this.log("xDelta = " + xDelta + ", yDelta = " + yDelta, "DATA");
  1246.             
  1247.         state = (aChoseTabs && chosenState) ? chosenState : state.split("\n")[4];
  1248.             
  1249.         var startup = (aMode == "startup");
  1250.         var newWindow = false;
  1251.         var overwriteTabs = true;
  1252.         var tabsToMove = null;
  1253.         var noUndoData = this.getNoUndoData(true, aMode);
  1254.  
  1255.         // gSingleWindowMode is set if Tab Mix Plus's single window mode is enabled
  1256.         var TMP_SingleWindowMode = (typeof(gSingleWindowMode) != "undefined" && gSingleWindowMode);
  1257.         if (TMP_SingleWindowMode) this.log("Tab Mix Plus single window mode is enabled", "INFO");
  1258.  
  1259.         // Use only existing window if our preference to do so is set or Tab Mix Plus's single window mode is enabled
  1260.         var singleWindowMode = (this.mPref_append_by_default && (aMode != "newwindow")) || TMP_SingleWindowMode;
  1261.     
  1262.         if (singleWindowMode && (aMode == "newwindow" || (!startup && (aMode != "overwrite") && !this.mPref_overwrite)))
  1263.             aMode = "append";
  1264.         
  1265.         // Use specified mode or default.
  1266.         aMode = aMode || "default";
  1267.         
  1268.         if (startup)
  1269.         {
  1270.             overwriteTabs = this.isCmdLineEmpty();
  1271.             tabsToMove = (!overwriteTabs)?Array.slice(gBrowser.mTabs):null;
  1272.         }
  1273.         else if (!overwrite_window && (aMode == "append"))
  1274.         {
  1275.             overwriteTabs = false;
  1276.         }
  1277.         else if (!singleWindowMode && (aMode == "newwindow" || (aMode != "overwrite" && !this.mPref_overwrite)))
  1278.         {
  1279.             // if there is only a blank window with no closed tabs, just use that instead of opening a new window
  1280.             var tabs = window.getBrowser();
  1281.             if (force_new_window || this.getBrowserWindows().length != 1 || !tabs || tabs.mTabs.length > 1 || 
  1282.                 tabs.mTabs[0].linkedBrowser.currentURI.spec != "about:blank" || 
  1283.                 this.mSessionStore.getClosedTabCount(window) > 0) {
  1284.                 newWindow = true;
  1285.             }
  1286.         }
  1287.         
  1288.         // Handle case where trying to restore to a newly opened window and Tab Mix Plus's Single Window Mode is active.
  1289.         // TMP is going to close this window after the restore, so restore into existing window
  1290.         var altWindow = null;
  1291.         if (TMP_SingleWindowMode) {
  1292.             var windows = this.getBrowserWindows();
  1293.             if (windows.length == 2) {
  1294.                 this.log("load: Restoring window into existing window because TMP single window mode active", "INFO");
  1295.                 if (windows[0] == window) altWindow = windows[1];
  1296.                 else altWindow = windows[0];
  1297.                 overwriteTabs = false;
  1298.             }
  1299.         }
  1300.  
  1301.         // Check whether or not to close open auto and window sessions.
  1302.         // Don't save current session on startup since there isn't any.  Don't save unless 
  1303.         // overwriting existing window(s) since nothing is lost in that case.
  1304.         if (!startup) {
  1305.             if ((!newWindow && overwriteTabs) || overwrite_window) {
  1306.                 // close current window sessions if open
  1307.                 if (this.__window_session_name) 
  1308.                 {
  1309.                     this.closeSession(true);
  1310.                 }
  1311.             }
  1312.             if (!newWindow && overwriteTabs && !overwrite_window)
  1313.             {
  1314.                 // Closed all open window sessions
  1315.                 var abandonBool = Components.classes["@mozilla.org/supports-PRBool;1"].createInstance(Components.interfaces.nsISupportsPRBool);
  1316.                 abandonBool.data = false;
  1317.                 this.mObserverService.notifyObservers(abandonBool, "sessionmanager:close-windowsession", null);
  1318.             
  1319.                 // close current autosave session if open
  1320.                 if (this.mPref__autosave_name) 
  1321.                 {
  1322.                     this.closeSession(false);
  1323.                 }
  1324.                 else 
  1325.                 {
  1326.                     if (this.mPref_autosave_session) this.autoSaveCurrentSession();
  1327.                 }
  1328.             }
  1329.         }
  1330.         
  1331.         // If not in private browser mode and did not choose tabs and not appending to current window
  1332.         if (!aChoseTabs && !this.isPrivateBrowserMode() && overwriteTabs && !altWindow)
  1333.         {
  1334.             // if this is a window session, keep track of it
  1335.             if (/^window\/?(\d*)$/.test(matchArray[3])) {
  1336.                 var time = parseInt(RegExp.$1);
  1337.                 window_autosave_values = this.mergeAutoSaveValues(matchArray[1], matchArray[7], time);
  1338.                 this.log("load: window session", "INFO");
  1339.             }
  1340.         
  1341.             // If this is an autosave session, keep track of it if not opening it in a new window and if there is not already an active session
  1342.             if (!newWindow && !overwrite_window && this.mPref__autosave_name=="" && /^session\/?(\d*)$/.test(matchArray[3])) 
  1343.             {
  1344.                 var time = parseInt(RegExp.$1);
  1345.                 this.setPref("_autosave_values", this.mergeAutoSaveValues(matchArray[1], matchArray[7], time));
  1346.             }
  1347.         }
  1348.         
  1349.         // If reload tabs enabled and not offline, set the tabs to allow reloading
  1350.         if (this.mPref_reload && !this.mIOService.offline) {
  1351.             try {
  1352.                 state = this.decrypt(state);
  1353.                 if (!state) return;
  1354.         
  1355.                 var tempState = this.JSON_decode(state);
  1356.                 for (var i in tempState.windows) {
  1357.                     for (var j in tempState.windows[i].tabs) {
  1358.                         // Only tag web pages as allowed to reload (this excludes chrome, about, etc)
  1359.                         if (tempState.windows[i].tabs[j].entries && tempState.windows[i].tabs[j].entries.length != 0 &&
  1360.                             /^https?:\/\//.test(tempState.windows[i].tabs[j].entries[tempState.windows[i].tabs[j].index - 1].url)) {
  1361.                             if (!tempState.windows[i].tabs[j].extData) tempState.windows[i].tabs[j].extData = {};
  1362.                             tempState.windows[i].tabs[j].extData["session_manager_allow_reload"] = true;
  1363.                         }
  1364.                     }
  1365.                 }
  1366.                 state = this.JSON_encode(tempState);
  1367.             }
  1368.             catch (ex) { this.logError(ex); };
  1369.         }
  1370.  
  1371.         setTimeout(function() {
  1372.             var tabcount = gBrowser.mTabs.length;
  1373.             var okay = gSessionManager.restoreSession((!newWindow)?(altWindow?altWindow:window):null, state, overwriteTabs, noUndoData, (overwriteTabs && !newWindow && !singleWindowMode && !overwrite_window), 
  1374.                                                       (singleWindowMode || (!overwriteTabs && !startup)), startup, window_autosave_values, xDelta, yDelta);
  1375.             if (okay) {
  1376.                 gSessionManager.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  1377.  
  1378.                 if (tabsToMove)
  1379.                 {
  1380.                     var endPos = gBrowser.mTabs.length - 1;
  1381.                     tabsToMove.forEach(function(aTab) { gBrowser.moveTabTo(aTab, endPos); });
  1382.                 }
  1383.             }
  1384.             // failed to load so clear autosession in case user tried to load one
  1385.             else gSessionManager.setPref("_autosave_values", "");
  1386.         }, 0);
  1387.     },
  1388.  
  1389.     rename: function(aSession)
  1390.     {
  1391.         var values;
  1392.         if (aSession) values = { name: aSession, text: this.getSessionCache(aSession).name };
  1393.         else values = {};
  1394.         
  1395.         if (!this.prompt(this._string("rename_session"), this._string("rename_session_ok"), values, this._string("rename2_session")))
  1396.         {
  1397.             return;
  1398.         }
  1399.         var file = this.getSessionDir(values.name);
  1400.         var filename = this.makeFileName(values.text);
  1401.         var newFile = (filename != file.leafName)?this.getSessionDir(filename, true):null;
  1402.         
  1403.         try
  1404.         {
  1405.             if (!file || !file.exists()) throw new Error(this._string("file_not_found"));
  1406.         
  1407.             var state = this.readSessionFile(file);
  1408.             var oldname = null;
  1409.             // Get original name
  1410.             if (/^(\[SessionManager v2\])(?:\nname=(.*))?/m.test(state)) oldname = RegExp.$2;
  1411.             // remove group name if it was a backup session
  1412.             if (this.getSessionCache(values.name).backup) state = state.replace(/\tgroup=[^\t|^\n|^\r]+/m, "");
  1413.             this.writeFile(newFile || file, this.nameState(state, values.text));
  1414.             if (newFile)
  1415.             {
  1416.                 if (this.mPref_resume_session == file.leafName && this.mPref_resume_session != this.mBackupSessionName &&
  1417.                     this.mPref_resume_session != this.mAutoSaveSessionName)
  1418.                 {
  1419.                     this.setPref("resume_session", filename);
  1420.                 }
  1421.                 this.delFile(file);
  1422.             }
  1423.  
  1424.             // Update any renamed auto or window session
  1425.             this.updateAutoSaveSessions(oldname, values.text);
  1426.         }
  1427.         catch (ex)
  1428.         {
  1429.             this.ioError(ex);
  1430.         }
  1431.     },
  1432.     
  1433.     group: function(aSession, aNewGroup)
  1434.     {
  1435.         var values = { multiSelect: true, grouping: true };
  1436.         if (typeof(aNewGroup) == "undefined") {
  1437.             aSession = this.prompt(this._string("group_session"), this._string("group_session_okay"), values, this._string("group_session_text"));
  1438.         }
  1439.         else {
  1440.             values.name = aSession;
  1441.             values.group = aNewGroup;
  1442.         }
  1443.         
  1444.         if (aSession)
  1445.         {
  1446.             var auto_save_file_name = this.makeFileName(this.mPref__autosave_name);
  1447.             values.name.split("\n").forEach(function(aFileName) {
  1448.                 try
  1449.                 {
  1450.                     var file = this.getSessionDir(aFileName);
  1451.                     if (!file || !file.exists()) throw new Error(this._string("file_not_found"));
  1452.                     var state = this.readSessionFile(file);
  1453.                     state = state.replace(/(\tcount=\d+\/\d+)(\tgroup=[^\t|^\n|^\r]+)?/m, function($0, $1) { return $1 + (values.group ? ("\tgroup=" + values.group.replace(/\t/g, " ")) : ""); });
  1454.                     this.writeFile(file, state);
  1455.  
  1456.                     // Grouped active session
  1457.                     if (auto_save_file_name == aFileName)
  1458.                     {
  1459.                         this.setPref("_autosave_values", this.mergeAutoSaveValues(this.mPref__autosave_name, values.group, this.mPref__autosave_time));
  1460.                     }
  1461.                 }
  1462.                 catch (ex)
  1463.                 {
  1464.                     this.ioError(ex);
  1465.                 }
  1466.                 
  1467.             }, this);
  1468.         }
  1469.     },
  1470.  
  1471.     remove: function(aSession)
  1472.     {
  1473.         if (!aSession)
  1474.         {
  1475.             var values = { multiSelect: true, remove: true };
  1476.             aSession = this.selectSession(this._string("remove_session"), this._string("remove_session_ok"), values);
  1477.             
  1478.             // If user chose to delete specific windows and tabs in a session
  1479.             if (values.choseTabs) {
  1480.                 // Get windows and tabs that were not deleted
  1481.                 try
  1482.                 {
  1483.                     var file = this.getSessionDir(aSession);
  1484.                     if (file.exists()) {
  1485.                         var state = this.readSessionFile(file);
  1486.                         if (state) {
  1487.                             var matchArray = this.mSessionRegExp.exec(state);
  1488.                             if (matchArray) {
  1489.                                 state = state.split("\n");
  1490.                                 var count = this.getCount(this.mSMHelper.mSessionData);
  1491.                                 state[3] = state[3].replace(/\tcount=[1-9][0-9]*\/[1-9][0-9]*/, "\tcount=" + count.windows + "/" + count.tabs);
  1492.                                 state[4] = this.decryptEncryptByPreference(this.mSMHelper.mSessionData);
  1493.                                 state = state.join("\n");
  1494.                                 this.writeFile(file, state);
  1495.                             }
  1496.                         }
  1497.                     }
  1498.                 }
  1499.                 catch(ex) {
  1500.                     this.ioError(ex);
  1501.                 }
  1502.                 this.mSMHelper.setSessionData("");
  1503.                 aSession = null;
  1504.             }
  1505.         }
  1506.         if (aSession)
  1507.         {
  1508.             aSession.split("\n").forEach(function(aFileName) {
  1509.                 // If deleted autoload session, revert to no autoload session
  1510.                 if ((aFileName == this.mPref_resume_session) && (aFileName != this.mBackupSessionName)) {
  1511.                     this.setPref("resume_session", this.mBackupSessionName);
  1512.                     this.setPref("startup", 0);
  1513.                 }
  1514.                 // In case deleting an auto-save or window session, update browser data
  1515.                 this.updateAutoSaveSessions(this.getSessionCache(aFileName).name);
  1516.                 this.delFile(this.getSessionDir(aFileName));
  1517.             }, this);
  1518.         }
  1519.     },
  1520.  
  1521.     openFolder: function()
  1522.     {
  1523.         var dir = this.getSessionDir();
  1524.         try {
  1525.             // "Double click" the session directory to open it
  1526.             dir.launch();
  1527.         } catch (e) {
  1528.             try {
  1529.                 // If launch also fails (probably because it's not implemented), let the
  1530.                 // OS handler try to open the session directory
  1531.                 var uri = Components.classes["@mozilla.org/network/io-service;1"].
  1532.                           getService(Components.interfaces.nsIIOService).newFileURI(dir);
  1533.                 var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"].
  1534.                                   getService(Components.interfaces.nsIExternalProtocolService);
  1535.                 protocolSvc.loadUrl(uri);
  1536.             }
  1537.             catch (ex)
  1538.             {
  1539.                 this.ioError(ex);
  1540.             }
  1541.         }
  1542.     },
  1543.  
  1544.     openOptions: function()
  1545.     {
  1546.         var dialog = this.mWindowMediator.getMostRecentWindow("SessionManager:Options");
  1547.         if (dialog)
  1548.         {
  1549.             dialog.focus();
  1550.             return;
  1551.         }
  1552.         
  1553.         openDialog("chrome://sessionmanager/content/options.xul", "_blank", "chrome,titlebar,toolbar,centerscreen," + ((this.getPref("browser.preferences.instantApply", false, true))?"dialog=no":"modal"));
  1554.     },
  1555.  
  1556. /* ........ Undo Menu Event Handlers .............. */
  1557.  
  1558.     initUndo: function(aPopup, aStandAlone)
  1559.     {
  1560.         function get_(a_id) { return aPopup.getElementsByAttribute("_id", a_id)[0] || null; }
  1561.         
  1562.         var separator = get_("closed-separator");
  1563.         var label = get_("windows");
  1564.         
  1565.         for (var item = separator.previousSibling; item != label; item = separator.previousSibling)
  1566.         {
  1567.             aPopup.removeChild(item);
  1568.         }
  1569.         
  1570.         var defaultIcon = (this.mApplication.name.toUpperCase() == "SEAMONKEY") ? "chrome://sessionmanager/skin/bookmark-item.png" :
  1571.                                                                     "chrome://sessionmanager/skin/defaultFavicon.png";
  1572.         
  1573.         var encrypt_okay = true;
  1574.         // make sure user enters master password if using sessionmanager.dat
  1575.         if (!this.mUseSSClosedWindowList && this.mPref_encrypt_sessions) {
  1576.             try { 
  1577.                 this.mSecretDecoderRing.encryptString("");
  1578.             }
  1579.             catch(ex) {
  1580.                 encrypt_okay = false;
  1581.                 this.cryptError(this._string("decrypt_fail2"));
  1582.             }
  1583.         }
  1584.         
  1585.         if (encrypt_okay) {
  1586.             var badClosedWindowData = false;
  1587.             var closedWindows = this.getClosedWindows();
  1588.             closedWindows.forEach(function(aWindow, aIx) {
  1589.                 // Try to decrypt is using sessionmanager.dat, if can't then data is bad since we checked for master password above
  1590.                 var state = this.mUseSSClosedWindowList ? aWindow.state : this.decrypt(aWindow.state, true);
  1591.                 if (!state && !this.mUseSSClosedWindowList) {
  1592.                     // flag it for removal from the list and go to next entry
  1593.                     badClosedWindowData = true;
  1594.                     aWindow._decode_error = "crypt_error";
  1595.                     return;
  1596.                 }
  1597.                 state = this.JSON_decode(state, true);
  1598.             
  1599.                 // detect corrupt sessionmanager.dat file
  1600.                 if (state._JSON_decode_failed && !this.mUseSSClosedWindowList) {
  1601.                     // flag it for removal from the list and go to next entry
  1602.                     badClosedWindowData = true;
  1603.                     aWindow._decode_error = state._JSON_decode_error;
  1604.                     return;
  1605.                 }
  1606.             
  1607.                 // Get favicon
  1608.                 var image = defaultIcon;
  1609.                 if (state.windows[0].tabs[0].xultab)
  1610.                 {
  1611.                     var xultabData = state.windows[0].tabs[0].xultab.split(" ");
  1612.                     xultabData.forEach(function(bValue, bIndex) {
  1613.                         var data = bValue.split("=");
  1614.                         if (data[0] == "image") {
  1615.                             image = data[1];
  1616.                         }
  1617.                     }, this);
  1618.                 }
  1619.                 // Firefox 3.5 uses attributes instead of xultab
  1620.                 if (state.windows[0].tabs[0].attributes && state.windows[0].tabs[0].attributes.image)
  1621.                 {
  1622.                     image = state.windows[0].tabs[0].attributes.image;
  1623.                 }
  1624.                 // Trying to display a favicon for an https with an invalid certificate will throw up an exception box, so don't do that
  1625.                 // Firefox's about:sessionrestore also fails with authentication requests, but Session Manager seems okay with that so just
  1626.                 // use the work around for https.
  1627.                 if (/^https:/.test(image)) {
  1628.                     image = "moz-anno:favicon:" + image;
  1629.                 }
  1630.             
  1631.                 // Get tab count
  1632.                 var count = state.windows[0].tabs.length;
  1633.         
  1634.                 var menuitem = document.createElement("menuitem");
  1635.                 menuitem.setAttribute("class", "menuitem-iconic sessionmanager-closedtab-item");
  1636.                 menuitem.setAttribute("label", aWindow.name + " (" + count + ")");
  1637.                 menuitem.setAttribute("tooltiptext", aWindow.name + " (" + count + ")");
  1638.                 menuitem.setAttribute("index", "window" + aIx);
  1639.                 menuitem.setAttribute("image", image);
  1640.                 menuitem.setAttribute("oncommand", 'gSessionManager.undoCloseWindow(' + aIx + ', (event.shiftKey && (event.ctrlKey || event.metaKey))?"overwrite":(event.ctrlKey || event.metaKey)?"append":"");');
  1641.                 menuitem.setAttribute("onclick", 'gSessionManager.clickClosedUndoMenuItem(event);');
  1642.                 menuitem.setAttribute("contextmenu", "sessionmanager-undo-ContextMenu");
  1643.                 menuitem.setAttribute("crop", "center");
  1644.                 aPopup.insertBefore(menuitem, separator);
  1645.             }, this);
  1646.         
  1647.             // Remove any bad closed windows
  1648.             if (badClosedWindowData)
  1649.             {
  1650.                 var error = null;
  1651.                 for (var i=0; i < closedWindows.length; i++)
  1652.                 {
  1653.                     if (closedWindows[i]._decode_error)
  1654.                     {
  1655.                         error = closedWindows[i]._decode_error;
  1656.                         closedWindows.splice(i, 1);
  1657.                         this.storeClosedWindows_SM(closedWindows);
  1658.                         // Do this so we don't skip over the next entry because of splice
  1659.                         i--;
  1660.                     }
  1661.                 }
  1662.                 if (error == "crypt_error") {
  1663.                     this.cryptError(this._string("decrypt_fail1"));
  1664.                 }
  1665.                 else {
  1666.                     this.sessionError(error);
  1667.                 }
  1668.             }
  1669.         }
  1670.         
  1671.         label.hidden = !encrypt_okay || (closedWindows.length == 0);
  1672.         
  1673.         var listEnd = get_("end-separator");
  1674.         for (item = separator.nextSibling.nextSibling; item != listEnd; item = separator.nextSibling.nextSibling)
  1675.         {
  1676.             aPopup.removeChild(item);
  1677.         }
  1678.         
  1679.         var closedTabs = this.mSessionStore.getClosedTabData(window);
  1680.         var mClosedTabs = [];
  1681.         closedTabs = this.JSON_decode(closedTabs);
  1682.         closedTabs.forEach(function(aValue, aIndex) {
  1683.             mClosedTabs[aIndex] = { title:aValue.title, image:null, 
  1684.                                 url:aValue.state.entries[aValue.state.entries.length - 1].url }
  1685.             // Get favicon
  1686.             mClosedTabs[aIndex].image = defaultIcon;
  1687.             if (aValue.state.xultab)
  1688.             {
  1689.                 var xultabData = aValue.state.xultab.split(" ");
  1690.                 xultabData.forEach(function(bValue, bIndex) {
  1691.                     var data = bValue.split("=");
  1692.                     if (data[0] == "image") {
  1693.                         mClosedTabs[aIndex].image = data[1];
  1694.                     }
  1695.                 }, this);
  1696.             }
  1697.             // Firefox 3.5 uses attributes instead of xultab
  1698.             if (aValue.state.attributes && aValue.state.attributes.image)
  1699.             {
  1700.                 mClosedTabs[aIndex].image = aValue.state.attributes.image;
  1701.             }
  1702.             // Trying to display a favicon for an https with an invalid certificate will throw up an exception box, so don't do that
  1703.             // Firefox's about:sessionrestore also fails with authentication requests, but Session Manager seems okay with that so just
  1704.             // use the work around for https.
  1705.             if (/^https:/.test(mClosedTabs[aIndex].image)) {
  1706.                 mClosedTabs[aIndex].image = "moz-anno:favicon:" + mClosedTabs[aIndex].image;
  1707.             }
  1708.         }, this);
  1709.  
  1710.         mClosedTabs.forEach(function(aTab, aIx) {
  1711.             var menuitem = document.createElement("menuitem");
  1712.             menuitem.setAttribute("class", "menuitem-iconic sessionmanager-closedtab-item");
  1713.             menuitem.setAttribute("image", aTab.image);
  1714.             menuitem.setAttribute("label", aTab.title);
  1715.             menuitem.setAttribute("tooltiptext", aTab.title);
  1716.             menuitem.setAttribute("index", "tab" + aIx);
  1717.             menuitem.setAttribute("statustext", aTab.url);
  1718.             menuitem.addEventListener("DOMMenuItemActive", function(event) { document.getElementById("statusbar-display").setAttribute("label",aTab.url); }, false);
  1719.             menuitem.addEventListener("DOMMenuItemInactive",  function(event) { document.getElementById("statusbar-display").setAttribute("label",''); }, false); 
  1720.             menuitem.setAttribute("oncommand", 'undoCloseTab(' + aIx + ');');
  1721.             menuitem.setAttribute("crop", "center");
  1722.             // Removing closed tabs does not work in SeaMonkey so don't give option to do so.
  1723.             if (this.mApplication.name.toUpperCase() != "SEAMONKEY") {
  1724.                 menuitem.setAttribute("onclick", 'gSessionManager.clickClosedUndoMenuItem(event);');
  1725.                 menuitem.setAttribute("contextmenu", "sessionmanager-undo-ContextMenu");
  1726.             }
  1727.             aPopup.insertBefore(menuitem, listEnd);
  1728.         }, this);
  1729.         separator.nextSibling.hidden = (mClosedTabs.length == 0);
  1730.         separator.hidden = separator.nextSibling.hidden || label.hidden;
  1731.         
  1732.         var showPopup = closedWindows.length + mClosedTabs.length > 0;
  1733.         
  1734.         if (aStandAlone)
  1735.         {
  1736.             if (!showPopup)
  1737.             {
  1738.                 this.updateToolbarButton(false);
  1739.                 setTimeout(function(aPopup) { aPopup.parentNode.open = false; }, 0, aPopup);
  1740.             }
  1741.             else {
  1742.                 // Bug copies tooltiptext to children so specifically set tooltiptext for all children
  1743.                 this.fixBug374288(aPopup.parentNode);
  1744.             }
  1745.         }
  1746.  
  1747.         return showPopup;
  1748.     },
  1749.  
  1750.     undoCloseWindow: function(aIx, aMode)
  1751.     {
  1752.         var closedWindows = this.getClosedWindows();
  1753.         if (closedWindows[aIx || 0])
  1754.         {
  1755.             var state = closedWindows.splice(aIx || 0, 1)[0].state;
  1756.             
  1757.             // gSingleWindowMode is set if Tab Mix Plus's single window mode is active
  1758.             if (typeof(gSingleWindowMode) != "undefined" && gSingleWindowMode) aMode = "append";
  1759.  
  1760.             if (aMode == "overwrite")
  1761.             {
  1762.                 this.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  1763.             }
  1764.             
  1765.             // If using SessionStore closed windows list and doing a normal restore, just use SessionStore API
  1766.             if (this.mUseSSClosedWindowList && (aMode != "append") && (aMode != "overwrite")) {
  1767.                 this.mSessionStore.undoCloseWindow(aIx);
  1768.             }
  1769.             else {
  1770.                 var okay = this.restoreSession((aMode == "overwrite" || aMode == "append")?window:null, state, aMode != "append");
  1771.                 if (okay) {
  1772.                     this.storeClosedWindows(closedWindows, aIx);
  1773.                     this.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  1774.                 }
  1775.             }
  1776.         }
  1777.     },
  1778.  
  1779.     clickClosedUndoMenuItem: function(aEvent) 
  1780.     {
  1781.         // if ctrl/command right click, remove item from list
  1782.         if ((aEvent.button == 2) && (aEvent.ctrlKey || aEvent.metaKey))
  1783.         {
  1784.             this.removeUndoMenuItem(aEvent.originalTarget);
  1785.             aEvent.preventDefault();
  1786.             aEvent.stopPropagation();
  1787.         }
  1788.     },
  1789.     
  1790.     removeUndoMenuItem: function(aTarget)
  1791.     {    
  1792.         var aIx = null;
  1793.         var indexAttribute = aTarget.getAttribute("index");
  1794.         // removing window item
  1795.         if (indexAttribute.indexOf("window") != -1) {
  1796.             // get index
  1797.             aIx = indexAttribute.substring(6);
  1798.             
  1799.             // If Firefox bug 491577 is fixed and using built in closed window list, use SessionStore method.
  1800.             if (this.mUseSSClosedWindowList && (typeof(this.mSessionStore.forgetClosedWindow) != "undefined")) {
  1801.                 this.mSessionStore.forgetClosedWindow(aIx);
  1802.             }
  1803.             else {
  1804.                 // remove window from closed window list and tell other open windows
  1805.                 var closedWindows = this.getClosedWindows();
  1806.                 closedWindows.splice(aIx, 1);
  1807.                 this.storeClosedWindows(closedWindows, aIx);
  1808.             }
  1809.             this.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  1810.  
  1811.             // update the remaining entries
  1812.             this.updateClosedList(aTarget, aIx, closedWindows.length, "window");
  1813.         }
  1814.         // removing tab item
  1815.         else if (indexAttribute.indexOf("tab") != -1) {
  1816.             // get index
  1817.             aIx = indexAttribute.substring(3);
  1818.             
  1819.             // If Firefox bug 461634 is fixed use SessionStore method.
  1820.             if (typeof(this.mSessionStore.forgetClosedTab) != "undefined") {
  1821.                 this.mSessionStore.forgetClosedTab(window, aIx);
  1822.             }
  1823.             else {
  1824.                 // This code is based off of code in Tab Mix Plus
  1825.                 var state = { windows: [], _firstTabs: true };
  1826.  
  1827.                 // get closed-tabs from nsSessionStore
  1828.                 var closedTabs = this.JSON_decode(this.mSessionStore.getClosedTabData(window));
  1829.                 // purge closed tab at aIndex
  1830.                 closedTabs.splice(aIx, 1);
  1831.                 state.windows[0] = { _closedTabs : closedTabs };
  1832.  
  1833.                 // replace existing _closedTabs
  1834.                 this.mSessionStore.setWindowState(window, this.JSON_encode(state), false);
  1835.             }
  1836.  
  1837.             // the following forces SessionStore to save the state to disk which the above doesn't do for some reason.
  1838.             this.mSessionStore.setWindowValue(window, "SM_dummy_value","1");
  1839.             this.mSessionStore.deleteWindowValue(window, "SM_dummy_value");
  1840.             
  1841.             // update the remaining entries
  1842.             this.updateClosedList(aTarget, aIx, this.mSessionStore.getClosedTabCount(window), "tab");
  1843.         }
  1844.     },
  1845.     
  1846.     updateClosedList: function(aMenuItem, aIx, aClosedListLength, aType) 
  1847.     {
  1848.         // Get menu popup
  1849.         var popup = aMenuItem.parentNode;
  1850.  
  1851.         // remove item from list
  1852.         popup.removeChild(aMenuItem);
  1853.                     
  1854.         // Update toolbar button if no more tabs
  1855.         if (aClosedListLength == 0) 
  1856.         {
  1857.             popup.hidePopup();
  1858.             this.mObserverService.notifyObservers(window, "sessionmanager:windowtabopenclose", aType);
  1859.         }
  1860.         // otherwise adjust indexes
  1861.         else 
  1862.         {
  1863.             for (var i=0; i<popup.childNodes.length; i++)
  1864.             { 
  1865.                 var index = popup.childNodes[i].getAttribute("index");
  1866.                 if (index && index.substring(0,aType.length) == aType)
  1867.                 {
  1868.                     var indexNo = index.substring(aType.length);
  1869.                     if (parseInt(indexNo) > parseInt(aIx))
  1870.                     {
  1871.                         popup.childNodes[i].setAttribute("index",aType + (parseInt(indexNo) - 1).toString());
  1872.                     }
  1873.                 }
  1874.             }
  1875.         }
  1876.     },
  1877.  
  1878.     clearUndoList: function()
  1879.     {
  1880.         var max_tabs_undo = this.getPref("browser.sessionstore.max_tabs_undo", 10, true);
  1881.         
  1882.         this.setPref("browser.sessionstore.max_tabs_undo", 0, true);
  1883.         this.setPref("browser.sessionstore.max_tabs_undo", max_tabs_undo, true);
  1884.         // Check to see if the value was set correctly.  Tab Mix Plus will reset the max_tabs_undo preference 
  1885.         // to 10 when changing from 0 to any number.  See http://tmp.garyr.net/forum/viewtopic.php?t=10158
  1886.         if (this.getPref("browser.sessionstore.max_tabs_undo", 10, true) != max_tabs_undo) {
  1887.             this.setPref("browser.sessionstore.max_tabs_undo", max_tabs_undo, true);
  1888.         }
  1889.  
  1890.         if (this.mUseSSClosedWindowList) {
  1891.             var state = { windows: [ {} ], _closedWindows: [] };
  1892.             this.mSessionStore.setWindowState(window, this.JSON_encode(state), false);
  1893.         }
  1894.         else {
  1895.             this.clearUndoData("window");
  1896.         }
  1897.         
  1898.         // the following forces SessionStore to save the state to disk which isn't done for some reason.
  1899.         this.mSessionStore.setWindowValue(window, "SM_dummy_value","1");
  1900.         this.mSessionStore.deleteWindowValue(window, "SM_dummy_value");
  1901.         
  1902.         this.mObserverService.notifyObservers(null, "sessionmanager:windowtabopenclose", null);
  1903.     },
  1904.     
  1905. /* ........ Right click menu handlers .............. */
  1906.     group_popupInit: function(aPopup) {
  1907.         var childMenu = document.popupNode.menupopup || document.popupNode.lastChild;
  1908.         childMenu.hidePopup();
  1909.     },
  1910.     
  1911.     group_rename: function() {
  1912.         var filename = document.popupNode.getAttribute("filename");
  1913.         var parentMenu = document.popupNode.parentNode.parentNode;
  1914.         var group = filename ? ((parentMenu.id != "sessionmanager-toolbar") ? parentMenu.label : "")
  1915.                              : document.popupNode.getAttribute("label");
  1916.         var newgroup = { value: group };
  1917.         var dummy = {};
  1918.         this.mPromptService.prompt(window, this._string("rename_group"), null, newgroup, null, dummy);
  1919.         if (newgroup.value == this._string("backup_sessions")) {
  1920.             this.mPromptService.alert((this.mBundle)?window:null, this.mTitle, this._string("rename_fail"));
  1921.             return;
  1922.         }
  1923.         else if (newgroup.value != group) {
  1924.             // changing group for one session or multiple sessions?
  1925.             if (filename) this.group(filename, newgroup.value);
  1926.             else {
  1927.                 var sessions = this.getSessions();
  1928.                 sessions.forEach(function(aSession) {
  1929.                     if (aSession.group == group) {
  1930.                         this.group(aSession.fileName, newgroup.value);
  1931.                     }
  1932.                 }, this);
  1933.             }
  1934.         }
  1935.     },
  1936.     
  1937.     group_remove: function() {
  1938.         var group = document.popupNode.getAttribute("label");
  1939.         if (this.mPromptService.confirm(window, this.mTitle, this._string("delete_confirm_group"))) {
  1940.             
  1941.             var sessions = this.getSessions();
  1942.             var sessionsToDelete = [];
  1943.             sessions.forEach(function(aSession) {
  1944.                 if (aSession.group == group) {
  1945.                     sessionsToDelete.push(aSession.fileName);
  1946.                 }
  1947.             }, this);
  1948.             if (sessionsToDelete.length) {
  1949.                 sessionsToDelete = sessionsToDelete.join("\n");
  1950.                 this.remove(sessionsToDelete);
  1951.             }
  1952.         }
  1953.     },
  1954.  
  1955.     session_popupInit: function(aPopup) {
  1956.         function get_(a_id) { return aPopup.getElementsByAttribute("_id", a_id)[0] || null; }
  1957.         
  1958.         var current = (document.popupNode.getAttribute("disabled") == "true");
  1959.         var autosave = document.popupNode.getAttribute("autosave");
  1960.         var replace = get_("replace");
  1961.         
  1962.         replace.hidden = (this.getBrowserWindows().length == 1);
  1963.         
  1964.         // Disable saving in privacy mode or loaded auto-save session
  1965.         var inPrivateBrowsing = this.isPrivateBrowserMode();
  1966.         this.setDisabled(replace, (inPrivateBrowsing | current));
  1967.         this.setDisabled(get_("replacew"), (inPrivateBrowsing | current));
  1968.         
  1969.         // Disable almost everything for currently loaded auto-save session
  1970.         this.setDisabled(get_("loadaw"), current);
  1971.         this.setDisabled(get_("loada"), current);
  1972.         this.setDisabled(get_("loadr"), current);
  1973.  
  1974.         // Hide change group choice for backup items        
  1975.         get_("changegroup").hidden = (document.popupNode.getAttribute("backup-item") == "true")
  1976.         
  1977.         // Hide option to close or abandon sessions if they aren't loaded
  1978.         get_("closer").hidden = get_("abandon").hidden = !current || (autosave != "session");
  1979.         get_("closer_window").hidden = get_("abandon_window").hidden = !current || (autosave != "window");
  1980.         get_("close_separator").hidden = get_("closer").hidden && get_("closer_window").hidden;
  1981.         
  1982.         // Disable setting startup if already startup
  1983.         this.setDisabled(get_("startup"), ((this.mPref_startup == 2) && (document.popupNode.getAttribute("filename") == this.mPref_resume_session)));
  1984.         
  1985.         // If Tab Mix Plus's single window mode is enabled, hide options to load into new windows
  1986.         get_("loada").hidden = (typeof(gSingleWindowMode) != "undefined" && gSingleWindowMode);
  1987.     },
  1988.  
  1989.     session_close: function(aOneWindow, aAbandon) {
  1990.         if (aOneWindow) {
  1991.             var matchArray = /(\d\) )?(.*)   \(\d+\/\d+\)/.exec(document.popupNode.getAttribute("label"))
  1992.             if (matchArray && matchArray[2]) {
  1993.                 var abandonBool = Components.classes["@mozilla.org/supports-PRBool;1"].createInstance(Components.interfaces.nsISupportsPRBool);
  1994.                 abandonBool.data = (aAbandon == true);
  1995.                 this.mObserverService.notifyObservers(abandonBool, "sessionmanager:close-windowsession", matchArray[2]);
  1996.             }
  1997.         }
  1998.         else {
  1999.             if (aAbandon) this.abandonSession();
  2000.             else this.closeSession();
  2001.         }
  2002.     },
  2003.     
  2004.     session_load: function(aReplace, aOneWindow) {
  2005.         var session = document.popupNode.getAttribute("filename");
  2006.         var oldOverwrite = this.mPref_overwrite;
  2007.         this.mPref_overwrite = !!aReplace;
  2008.         this.load(session, (aReplace?"overwrite":(aOneWindow?"append":"newwindow")));
  2009.         this.mPref_overwrite = oldOverwrite;
  2010.     },
  2011.     
  2012.     session_replace: function(aOneWindow) {
  2013.         var session = document.popupNode.getAttribute("filename");
  2014.         var parent = document.popupNode.parentNode.parentNode;
  2015.         var group = null;
  2016.         if (parent.id.indexOf("sessionmanager-") == -1) {
  2017.             group = parent.label;
  2018.         }
  2019.         if (aOneWindow) {
  2020.             this.saveWindow(this.getSessionCache(session).name, session, group);
  2021.         }
  2022.         else {
  2023.             this.save(this.getSessionCache(session).name, session, group);
  2024.         }
  2025.     },
  2026.     
  2027.     session_rename: function() {
  2028.         var session = document.popupNode.getAttribute("filename");
  2029.         this.rename(session);
  2030.     },
  2031.  
  2032.     session_remove: function() {
  2033.         var dontPrompt = { value: false };
  2034.         var session = document.popupNode.getAttribute("filename");
  2035.         if (this.getPref("no_delete_prompt") || this.mPromptService.confirmEx(window, this.mTitle, this._string("delete_confirm"), this.mPromptService.BUTTON_TITLE_YES * this.mPromptService.BUTTON_POS_0 + this.mPromptService.BUTTON_TITLE_NO * this.mPromptService.BUTTON_POS_1, null, null, null, this._string("prompt_not_again"), dontPrompt) == 0) {
  2036.             this.remove(session);
  2037.             if (dontPrompt.value) {
  2038.                 this.setPref("no_delete_prompt", true);
  2039.             }
  2040.         }
  2041.     },
  2042.     
  2043.     session_setStartup: function() {
  2044.         var session = document.popupNode.getAttribute("filename");
  2045.         this.setPref("resume_session", session);
  2046.         this.setPref("startup", 2);
  2047.     },
  2048.     
  2049.     hidePopup: function() {
  2050.         var popup = document.popupNode.parentNode;
  2051.         while (popup.parentNode.id.indexOf("sessionmanager-") == -1) {
  2052.             popup = popup.parentNode;
  2053.         }
  2054.         if (popup.parentNode.id != "sessionmanager-toolbar" ) popup = popup.parentNode.parentNode;
  2055.         popup.hidePopup();
  2056.     },
  2057.     
  2058. /* ........ User Prompts .............. */
  2059.  
  2060.     openSessionExplorer: function() {
  2061.         this.openWindow(
  2062. //            "chrome://sessionmanager/content/sessionexplorer.xul",
  2063.             "chrome://sessionmanager/content/places/places.xul",
  2064.             "chrome,titlebar,resizable,dialog=yes",
  2065.             {},
  2066.             (this.mFullyLoaded)?window:null
  2067.         );
  2068.     },
  2069.  
  2070.     prompt: function(aSessionLabel, aAcceptLabel, aValues, aTextLabel, aAcceptExistingLabel)
  2071.     {
  2072.         var params = Components.classes["@mozilla.org/embedcomp/dialogparam;1"].createInstance(Components.interfaces.nsIDialogParamBlock);
  2073.         aValues = aValues || {};
  2074.         
  2075.         params.SetNumberStrings(8);
  2076.         params.SetString(1, aSessionLabel);
  2077.         params.SetString(2, aAcceptLabel);
  2078.         params.SetString(3, aValues.name || "");
  2079.         params.SetString(4, aTextLabel || "");
  2080.         params.SetString(5, aAcceptExistingLabel || "");
  2081.         params.SetString(6, aValues.text || "");
  2082.         params.SetString(7, aValues.count || "");
  2083.         params.SetInt(1, ((aValues.addCurrentSession)?1:0) | ((aValues.multiSelect)?2:0) | ((aValues.ignorable)?4:0) | 
  2084.                           ((aValues.autoSaveable)?8:0) | ((aValues.remove)?16:0) | ((aValues.grouping)?32:0) |
  2085.                           ((aValues.append_replace)?64:0) | ((aValues.preselect)?128:0) | ((aValues.allowNamedReplace)?256:0));
  2086.         
  2087.         this.openWindow("chrome://sessionmanager/content/session_prompt.xul", "chrome,titlebar,centerscreen,modal,resizable,dialog=yes", params, (this.mFullyLoaded)?window:null);
  2088.         
  2089.         aValues.name = params.GetString(3);
  2090.         aValues.text = params.GetString(6);
  2091.         aValues.group = params.GetString(7);
  2092.         aValues.ignore = (params.GetInt(1) & 4)?1:0;
  2093.         aValues.autoSave = (params.GetInt(1) & 8)?1:0;
  2094.         aValues.choseTabs = (params.GetInt(1) & 16)?1:0;
  2095.         aValues.append = (params.GetInt(1) & 32)?1:0;
  2096.         aValues.append_window = (params.GetInt(1) & 64)?1:0;
  2097.         aValues.autoSaveTime = params.GetInt(2) | null;
  2098.         return params.GetInt(0);
  2099.     },
  2100.     
  2101.     // the aOverride variable in an optional callback procedure that will be used to get the session list instead
  2102.     // of the default getSessions() function.  The function must return an array of sessions where a session is an
  2103.     // object containing:
  2104.     //        name         - This is what is displayed in the session select window
  2105.     //        fileName    - This is what is returned when the object is selected
  2106.     //        windows        - Window count (optional - if omited won't display either window or tab count)
  2107.     //        tabs        - Tab count    (optional - if omited won't display either window or tab count)
  2108.     //        autosave    - Will cause item to be bold (optional)
  2109.     //      group       - Group that session is associated with (optional)
  2110.     //
  2111.     // If the session list is not formatted correctly a message will be displayed in the Error console
  2112.     // and the session select window will not be displayed.
  2113.     //
  2114.     selectSession: function(aSessionLabel, aAcceptLabel, aValues, aOverride)
  2115.     {
  2116.         var values = aValues || {};
  2117.         
  2118.         if (aOverride) this.getSessionsOverride = aOverride;
  2119.         
  2120.         if (this.prompt(aSessionLabel, aAcceptLabel, values))
  2121.         {
  2122.             this.getSessionsOverride = null;
  2123.             return values.name;
  2124.         }
  2125.         
  2126.         this.getSessionsOverride = null;
  2127.         return null;
  2128.     },
  2129.  
  2130.     ioError: function(aException)
  2131.     {
  2132.         if (aException) this.logError(aException);
  2133.         this.mPromptService.alert((this.mBundle)?window:null, this.mTitle, (this.mBundle)?this.mBundle.getFormattedString("io_error", [(aException)?aException.message:this._string("unknown_error")]):aException);
  2134.     },
  2135.  
  2136.     sessionError: function(aException)
  2137.     {
  2138.         if (aException) this.logError(aException);
  2139.         this.mPromptService.alert((this.mBundle)?window:null, this.mTitle, (this.mBundle)?this.mBundle.getFormattedString("session_error", [(aException)?aException.message:this._string("unknown_error")]):aException);
  2140.     },
  2141.  
  2142.     openWindow: function(aChromeURL, aFeatures, aArgument, aParent)
  2143.     {
  2144.         if (!aArgument || typeof aArgument == "string")
  2145.         {
  2146.             var argString = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
  2147.             argString.data = aArgument || "";
  2148.             aArgument = argString;
  2149.         }
  2150.         
  2151.         return Components.classes["@mozilla.org/embedcomp/window-watcher;1"].getService(Components.interfaces.nsIWindowWatcher).openWindow(aParent || null, aChromeURL, "_blank", aFeatures, aArgument);
  2152.     },
  2153.  
  2154.     clearUndoListPrompt: function()
  2155.     {
  2156.         var dontPrompt = { value: false };
  2157.         if (this.getPref("no_clear_list_prompt") || this.mPromptService.confirmEx(null, this.mTitle, this._string("clear_list_prompt"), this.mPromptService.BUTTON_TITLE_YES * this.mPromptService.BUTTON_POS_0 + this.mPromptService.BUTTON_TITLE_NO * this.mPromptService.BUTTON_POS_1, null, null, null, this._string("prompt_not_again"), dontPrompt) == 0)
  2158.         {
  2159.             this.clearUndoList();
  2160.             if (dontPrompt.value)
  2161.             {
  2162.                 this.setPref("no_clear_list_prompt", true);
  2163.             }
  2164.         }
  2165.     },
  2166.     
  2167. /* ........ File Handling .............. */
  2168.     convertToSQL: function() {
  2169.         // Open SQL file and connect to it
  2170.         var file = Components.classes["@mozilla.org/file/directory_service;1"]
  2171.                    .getService(Components.interfaces.nsIProperties)
  2172.                    .get("ProfD", Components.interfaces.nsIFile);
  2173.         file.append("sessionmanager.sqlite");
  2174.         this.delFile(file, true);
  2175.  
  2176.         // delete this after testing
  2177.         var date = new Date();
  2178.         var begin = date.getTime();
  2179.         
  2180.         var storageService = Components.classes["@mozilla.org/storage/service;1"]
  2181.                              .getService(Components.interfaces.mozIStorageService);
  2182.         var mDBConn = storageService.openDatabase(file); 
  2183.  
  2184.         mDBConn.createTable("sessions", "filename TEXT PRIMARY KEY, name TEXT, groupname TEXT, timestamp INTEGER," +
  2185.                              "autosave TEXT, windows INTEGER, tabs INTEGER, backup INTEGER, state BLOB");
  2186.  
  2187.         mDBConn.createTable("closed_windows", "id INTEGER PRIMARY KEY, name TEXT, state BLOB");
  2188.         
  2189.         var sessions = this.getSessions();
  2190.  
  2191.         var everythingOkay = true;
  2192.         mDBConn.beginTransaction();
  2193.         
  2194.         sessions.forEach(function(aSession) {
  2195.             
  2196.             if (everythingOkay) {
  2197.                 var file = this.getSessionDir(aSession.fileName);
  2198.                 var state = this.readSessionFile(file);
  2199.                 if (state) 
  2200.                 {
  2201.                     if (this.mSessionRegExp.test(state))
  2202.                     {
  2203.                         state = state.split("\n")
  2204.                     }
  2205.                 }
  2206.                 
  2207.                 if (state[4]) {
  2208.                     // Just replace whatever's there since the filename is unique
  2209.                     var statement = mDBConn.createStatement(
  2210.                         "INSERT INTO sessions (filename, name, groupname, timestamp, autosave, windows, tabs, backup, state) " +
  2211.                         "VALUES ( :filename, :name, :groupname, :timestamp, :autosave, :windows, :tabs, :backup, :state )"
  2212.                     );
  2213.                     // need to wrap in older versions of Firefox
  2214.                     if (this.mVersionCompare.compare(this.mPlatformVersion,"1.9.1a1pre") < 0) {
  2215.                         var wrapper = Components.classes["@mozilla.org/storage/statement-wrapper;1"]
  2216.                                       .createInstance(Components.interfaces.mozIStorageStatementWrapper);
  2217.                         wrapper.initialize(statement);
  2218.                         statement = wrapper;
  2219.                     }
  2220.                     statement.params.filename = aSession.fileName;
  2221.                     statement.params.name = aSession.name;
  2222.                     statement.params.groupname = aSession.group;
  2223.                     statement.params.timestamp = aSession.timestamp;
  2224.                     statement.params.autosave = aSession.autosave;
  2225.                     statement.params.windows = aSession.windows;
  2226.                     statement.params.tabs = aSession.tabs;
  2227.                     statement.params.backup = aSession.backup ? 1 : 0;
  2228.                     statement.params.state = state[4];
  2229.                     try {
  2230.                         statement.execute();
  2231.                     }
  2232.                     catch(ex) { 
  2233.                         everythingOkay = false;
  2234.                         this.log("convertToSQL: " + aSession.fileName + " - " + ex, "ERROR", true);
  2235.                     }
  2236.                     finally {
  2237.                         if (this.mVersionCompare.compare(this.mPlatformVersion,"1.9.1a1pre") < 0) {
  2238.                             statement.statement.finalize();
  2239.                         }
  2240.                         else {
  2241.                             statement.finalize();
  2242.                         }
  2243.                     }
  2244.                 }
  2245.             }
  2246.         }, this);
  2247.  
  2248.         var closedWindows = this.getClosedWindows_SM();
  2249.         closedWindows.forEach(function(aWindow) {
  2250.             var statement = mDBConn.createStatement("INSERT INTO closed_windows (name, state) VALUES (:name, :state)");
  2251.             // need to wrap in older versions of Firefox
  2252.             if (this.mVersionCompare.compare(this.mPlatformVersion,"1.9.1a1pre") < 0) {
  2253.                 var wrapper = Components.classes["@mozilla.org/storage/statement-wrapper;1"]
  2254.                               .createInstance(Components.interfaces.mozIStorageStatementWrapper);
  2255.                 statement = wrapper.initialize(statement);
  2256.             }
  2257.             statement.params.name = aWindow.name;
  2258.             statement.params.state = aWindow.state;
  2259.             try {
  2260.                 statement.execute();
  2261.             }
  2262.             catch(ex) { 
  2263.                 everythingOkay = false;
  2264.                 this.log("convertToSQL" + aWindow.name + " - " + ex, "ERROR", true);
  2265.             }
  2266.             finally {
  2267.                 if (this.mVersionCompare.compare(this.mPlatformVersion,"1.9.1a1pre") < 0) {
  2268.                     statement.statement.finalize();
  2269.                 }
  2270.                 else {
  2271.                     statement.finalize();
  2272.                 }
  2273.             }
  2274.         });
  2275.         
  2276.         // if everything's good save everything, otherwise undo it
  2277.         if (everythingOkay) {
  2278.             mDBConn.commitTransaction();
  2279.             // delete this after testing
  2280.             var date = new Date();
  2281.             var end = date.getTime();
  2282.             Components.utils.reportError("Session Manager: Converted to SQL in " + (end - begin) + " ms");
  2283.         }
  2284.         else {
  2285.             mDBConn.rollbackTransaction();
  2286.             // delete this after testing
  2287.             Components.utils.reportError("Session Manager: Error converting to SQL");
  2288.         }
  2289.         mDBConn.close();
  2290.     },
  2291.  
  2292.     sanitize: function()
  2293.     {
  2294.         // If Sanitize GUI not used (or not Firefox 3.5 and above)
  2295.         if (this.mSanitizePreference == "privacy.item.extensions-sessionmanager") {
  2296.             // Remove all saved sessions
  2297.             this.getSessionDir().remove(true);
  2298.         }
  2299.         else {
  2300.             Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader)
  2301.                                                                        .loadSubScript("chrome://browser/content/sanitize.js");
  2302.  
  2303.             var range = Sanitizer.getClearRange();
  2304.            
  2305.             // if delete all, then do it.
  2306.             if (!range) {
  2307.                 // Remove all saved sessions
  2308.                 this.getSessionDir().remove(true);
  2309.             }
  2310.             else {
  2311.                 // Delete only sessions after startDate
  2312.                 var sessions = this.getSessions();
  2313.                 sessions.forEach(function(aSession, aIx) { 
  2314.                     if (range[0] <= aSession.timestamp*1000) {
  2315.                         this.delFile(this.getSessionDir(aSession.fileName));
  2316.                     }
  2317.                 }, this);
  2318.             }                 
  2319.         }
  2320.     },
  2321.  
  2322.     getProfileFile: function(aFileName)
  2323.     {
  2324.         var file = this.mProfileDirectory.clone();
  2325.         file.append(aFileName);
  2326.         return file;
  2327.     },
  2328.     
  2329.     getUserDir: function(aFileName)
  2330.     {
  2331.         var dir = null;
  2332.         var dirname = this.getPref("sessions_dir", "");
  2333.         try {
  2334.             if (dirname) {
  2335.                 var dir = this.mComponents.classes["@mozilla.org/file/local;1"].createInstance(this.mComponents.interfaces.nsILocalFile);
  2336.                 dir.initWithPath(dirname);
  2337.                 if (dir.isDirectory && dir.isWritable()) {
  2338.                     dir.append(aFileName);
  2339.                 }
  2340.                 else {
  2341.                     dir = null;
  2342.                 }
  2343.             }
  2344.         } catch (ex) {
  2345.             // handle the case on shutdown since the above will always throw an exception on shutdown
  2346.             if (this._mUserDirectory) dir = this._mUserDirectory.clone();
  2347.             else dir = null;
  2348.         } finally {
  2349.             return dir;
  2350.         }
  2351.     },
  2352.  
  2353.     getSessionDir: function(aFileName, aUnique)
  2354.     {
  2355.         // Check for absolute path first, session names can't have \ or / in them so this will work.  Relative paths will throw though.
  2356.         if (/[\\\/]/.test(aFileName)) {
  2357.             var file = this.mComponents.classes["@mozilla.org/file/local;1"].createInstance(this.mComponents.interfaces.nsILocalFile);
  2358.             try {
  2359.                 file.initWithPath(aFileName);
  2360.             }
  2361.             catch(ex) {
  2362.                 this.ioError(ex);
  2363.                 file = null;
  2364.             }
  2365.             return file;
  2366.         }
  2367.         else {
  2368.             // allow overriding of location of sessions directory
  2369.             var dir = this.getUserDir("sessions");
  2370.             
  2371.             // use default is not specified or not a writable directory
  2372.             if (dir == null) {
  2373.                 dir = this.getProfileFile("sessions");
  2374.             }
  2375.             if (!dir.exists())
  2376.             {
  2377.                 try {
  2378.                     dir.create(this.mComponents.interfaces.nsIFile.DIRECTORY_TYPE, 0700);
  2379.                 }
  2380.                 catch (ex) {
  2381.                     this.ioError(ex);
  2382.                     return null;
  2383.                 }
  2384.             }
  2385.             if (aFileName)
  2386.             {
  2387.                 dir.append(aFileName);
  2388.                 if (aUnique)
  2389.                 {
  2390.                     var postfix = 1, ext = "";
  2391.                     if (aFileName.slice(-this.mSessionExt.length) == this.mSessionExt)
  2392.                     {
  2393.                         aFileName = aFileName.slice(0, -this.mSessionExt.length);
  2394.                         ext = this.mSessionExt;
  2395.                     }
  2396.                     while (dir.exists())
  2397.                     {
  2398.                         dir = dir.parent;
  2399.                         dir.append(aFileName + "-" + (++postfix) + ext);
  2400.                     }
  2401.                 }
  2402.             }
  2403.             return dir.QueryInterface(this.mComponents.interfaces.nsILocalFile);
  2404.         }
  2405.     },
  2406.  
  2407.     //
  2408.     // filter - optional regular expression. If specified, will only return sessions that match that expression
  2409.     //
  2410.     getSessions: function(filter)
  2411.     {
  2412.         var matchArray;
  2413.         var sessions = [];
  2414.         sessions.latestTime = sessions.latestBackUpTime = 0;
  2415.         
  2416.         var filesEnum = this.getSessionDir().directoryEntries.QueryInterface(this.mComponents.interfaces.nsISimpleEnumerator);
  2417.         while (filesEnum.hasMoreElements())
  2418.         {
  2419.             var file = filesEnum.getNext().QueryInterface(this.mComponents.interfaces.nsIFile);
  2420.             // don't try to read a directory
  2421.             if (file.isDirectory()) continue;
  2422.             var fileName = file.leafName;
  2423.             var backupItem = (this.mBackupSessionRegEx.test(fileName) || (fileName == this.mAutoSaveSessionName));
  2424.             var cached = this.getSessionCache(fileName) || null;
  2425.             if (cached && cached.time == file.lastModifiedTime)
  2426.             {
  2427.                 try {
  2428.                     if (filter && !filter.test(cached.name)) continue;
  2429.                 } catch(ex) { 
  2430.                     this.log ("getSessions: Bad Regular Expression passed to getSessions, ignoring", true); 
  2431.                 }
  2432.                 if (!backupItem && (sessions.latestTime < cached.timestamp)) 
  2433.                 {
  2434.                     sessions.latestTime = cached.timestamp;
  2435.                 }
  2436.                 else if (backupItem && (sessions.latestBackUpTime < cached.timestamp)) {
  2437.                     sessions.latestBackUpTime = cached.timestamp;
  2438.                 }
  2439.                 sessions.push({ fileName: fileName, name: cached.name, timestamp: cached.timestamp, autosave: cached.autosave, windows: cached.windows, tabs: cached.tabs, backup: backupItem, group: cached.group });
  2440.                 continue;
  2441.             }
  2442.             if (matchArray = this.mSessionRegExp.exec(this.readSessionFile(file, true)))
  2443.             {
  2444.                 try {
  2445.                     if (filter && !filter.test(matchArray[1])) continue;
  2446.                 } catch(ex) { 
  2447.                     this.log ("getSessions: Bad Regular Expression passed to getSessions, ignoring", true); 
  2448.                 }
  2449.                 var timestamp = parseInt(matchArray[2]) || file.lastModifiedTime;
  2450.                 if (!backupItem && (sessions.latestTime < timestamp)) 
  2451.                 {
  2452.                     sessions.latestTime = timestamp;
  2453.                 }
  2454.                 else if (backupItem && (sessions.latestBackUpTime < timestamp)) {
  2455.                     sessions.latestBackUpTime = timestamp;
  2456.                 }
  2457.                 var group = matchArray[7] ? matchArray[7] : "";
  2458.                 sessions.push({ fileName: fileName, name: matchArray[1], timestamp: timestamp, autosave: matchArray[3], windows: matchArray[4], tabs: matchArray[5], backup: backupItem, group: group });
  2459.                 // cache session data unless browser is shutting down
  2460.                 if (!this.mPref__stopping) this.setSessionCache(fileName, { name: matchArray[1], timestamp: timestamp, autosave: matchArray[3], time: file.lastModifiedTime, windows: matchArray[4], tabs: matchArray[5], backup: backupItem, group: group });
  2461.             }
  2462.         }
  2463.         
  2464.         if (!this.mPref_session_list_order)
  2465.         {
  2466.             this.mPref_session_list_order = this.getPref("session_list_order", 1);
  2467.         }
  2468.         switch (Math.abs(this.mPref_session_list_order))
  2469.         {
  2470.         case 1: // alphabetically
  2471.             sessions = sessions.sort(function(a, b) { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); });
  2472.             break;
  2473.         case 2: // chronologically
  2474.             sessions = sessions.sort(function(a, b) { return a.timestamp - b.timestamp; });
  2475.             break;
  2476.         }
  2477.         
  2478.         return (this.mPref_session_list_order < 0)?sessions.reverse():sessions;
  2479.     },
  2480.  
  2481.     getClosedWindowsCount: function() {
  2482.         return this.getClosedWindows(true);
  2483.     },
  2484.     
  2485.     // Get SessionStore's or Session Manager's Closed window List depending on preference.
  2486.     // Return the length if the Length Only parameter is true - only ever true if not using built in closed window list
  2487.     getClosedWindows: function(aLengthOnly)
  2488.     {
  2489.         if (this.mUseSSClosedWindowList) {
  2490.             var closedWindows = this.JSON_decode(this.mSessionStore.getClosedWindowData());
  2491.             if (aLengthOnly) return closedWindows.length;
  2492.             var parts = new Array(closedWindows.length);
  2493.             closedWindows.forEach(function(aWindow, aIx) {
  2494.                 parts[aIx] = { name: aWindow.title, state: this.JSON_encode({windows:[aWindow]}) };
  2495.             }, this);
  2496.             return parts;
  2497.         }
  2498.         else {
  2499.             return this.getClosedWindows_SM(aLengthOnly);
  2500.         }
  2501.     },
  2502.  
  2503.     getClosedWindows_SM: function(aLengthOnly)
  2504.     {
  2505.         // Use cached data unless file has changed or was deleted
  2506.         var data = null;
  2507.         var file = this.getProfileFile(this.mClosedWindowFile);
  2508.         if (!file.exists()) return (aLengthOnly ? 0 : []);
  2509.         else if (file.lastModifiedTime > this.getClosedWindowCache(false)) {
  2510.             data = this.readFile(this.getProfileFile(this.mClosedWindowFile));
  2511.             this.setClosedWindowCache(data, file.lastModifiedTime);
  2512.             if (aLengthOnly) return (data ? data.split("\n\n").length : 0);
  2513.         }
  2514.         else {
  2515.             data = this.getClosedWindowCache(true, aLengthOnly);
  2516.             if (aLengthOnly) return data;
  2517.         }
  2518.         
  2519.         return (data)?data.split("\n\n").map(function(aEntry) {
  2520.             var parts = aEntry.split("\n");
  2521.             return { name: parts.shift(), state: parts.join("\n") };
  2522.         }):[];
  2523.     },
  2524.  
  2525.     // Stored closed windows into Session Store or Session Manager controller list.
  2526.     storeClosedWindows: function(aList, aIx)
  2527.     {
  2528.         if (this.mUseSSClosedWindowList) {
  2529.             // The following works in that the closed window appears to be removed from the list with no side effects
  2530.             var closedWindows = this.JSON_decode(this.mSessionStore.getClosedWindowData());
  2531.             closedWindows.splice(aIx || 0, 1);
  2532.             var state = { windows: [ {} ], _closedWindows: closedWindows };
  2533.             this.mSessionStore.setWindowState(window, this.JSON_encode(state), false);
  2534.             // the following forces SessionStore to save the state to disk which the above doesn't do for some reason.
  2535.             this.mSessionStore.setWindowValue(window, "SM_dummy_value","1");
  2536.             this.mSessionStore.deleteWindowValue(window, "SM_dummy_value");
  2537.         }
  2538.         else {
  2539.             this.storeClosedWindows_SM(aList);
  2540.         }
  2541.     },
  2542.  
  2543.     // Store closed windows into Session Manager controlled list
  2544.     storeClosedWindows_SM: function(aList)
  2545.     {
  2546.         var file = this.getProfileFile(this.mClosedWindowFile);
  2547.         if (aList.length > 0)
  2548.         {
  2549.             var data = aList.map(function(aEntry) {
  2550.                 return aEntry.name + "\n" + aEntry.state
  2551.             }).join("\n\n");
  2552.             try {
  2553.                 this.writeFile(file, data);
  2554.                 this.setClosedWindowCache(data, file.lastModifiedTime);
  2555.             }
  2556.             catch(ex) {
  2557.                 this.ioError(ex);
  2558.                 return;
  2559.             }
  2560.         }
  2561.         else
  2562.         {
  2563.             try {
  2564.                 this.delFile(file);
  2565.                 this.setClosedWindowCache(null, 0);
  2566.             }
  2567.             catch(ex) {
  2568.                 this.ioError(ex);
  2569.                 return;
  2570.             }
  2571.         }
  2572.         
  2573.         this.updateToolbarButton(aList.length + this.mSessionStore.getClosedTabCount(window)  > 0);
  2574.     },
  2575.  
  2576.     appendClosedWindow: function(aState)
  2577.     {
  2578.         var cleanBrowser = (this.mCleanBrowser != null) ? this.mCleanBrowser : Array.every(gBrowser.browsers, this.isCleanBrowser);
  2579.         if (this.mPref_max_closed_undo == 0 || this.isPrivateBrowserMode() || cleanBrowser)
  2580.         {
  2581.             return;
  2582.         }
  2583.         
  2584.         var name = this.mClosedWindowName || content.document.title || ((gBrowser.currentURI.spec != "about:blank")?gBrowser.currentURI.spec:this._string("untitled_window"));
  2585.         var windows = this.getClosedWindows_SM();
  2586.         
  2587.         // encrypt state if encryption preference set
  2588.         if (this.mPref_encrypt_sessions) {
  2589.             aState = this.decryptEncryptByPreference(aState);
  2590.             if (!aState) return;
  2591.         }
  2592.                 
  2593.         aState = aState.replace(/^\n+|\n+$/g, "").replace(/\n{2,}/g, "\n");
  2594.         windows.unshift({ name: name, state: aState });
  2595.         this.storeClosedWindows_SM(windows.slice(0, this.mPref_max_closed_undo));
  2596.     },
  2597.  
  2598.     clearUndoData: function(aType, aSilent, aShuttingDown)
  2599.     {
  2600.         if (aType == "window" || aType == "all")
  2601.         {
  2602.             this.delFile(this.getProfileFile(this.mClosedWindowFile), aSilent);
  2603.         }
  2604.         if (!aShuttingDown) this.updateToolbarButton((aType == "all")?false:undefined);
  2605.     },
  2606.  
  2607.     shutDown: function()
  2608.     {
  2609.         this.log("Shutdown start", "TRACE");
  2610.         // Handle sanitizing if sanitize on shutdown without prompting (Firefox 3.5 never prompts)
  2611.         var prompt = this.getPref("privacy.sanitize.promptOnSanitize", null, true);
  2612.         var sanitize = (this.getPref("privacy.sanitize.sanitizeOnShutdown", false, true) && 
  2613.                        (((prompt == false) && this.getPref("privacy.item.extensions-sessionmanager", false, true)) ||
  2614.                         ((prompt == null) && this.getPref("privacy.clearOnShutdown.extensions-sessionmanager", false, true))));
  2615.  
  2616.         if (sanitize)
  2617.         {
  2618.             this.sanitize();
  2619.         }
  2620.         // otherwise
  2621.         else
  2622.         {
  2623.             // If preference to clear save windows or using SessionStore closed windows, delete our closed window list
  2624.             if (!this.mPref_save_window_list || this.mUseSSClosedWindowList)
  2625.             {
  2626.                 this.clearUndoData("window", true, true);
  2627.             }
  2628.             
  2629.             // Don't back up if in private browsing mode automatically via privacy preference
  2630.             var nobackup = this.mSMHelper.mAutoPrivacy && (this.mShutDownInPrivateBrowsingMode || this.isPrivateBrowserMode());
  2631.         
  2632.             // save the currently opened session (if there is one) otherwise backup if auto-private browsing mode not enabled
  2633.             if (!this.closeSession(false) && !nobackup)
  2634.             {
  2635.                 this.backupCurrentSession();
  2636.             }
  2637.             else
  2638.             {
  2639.                 this.keepOldBackups(false);
  2640.             }
  2641.             
  2642.             this.delFile(this.getSessionDir(this.mAutoSaveSessionName), true);
  2643.         }
  2644.         
  2645.         this.delPref("_autosave_values");
  2646.         this.delPref("_encrypt_file");
  2647.         this.delPref("_recovering");
  2648.         this.mClosingWindowState = null;
  2649.         this.mCleanBrowser = null;
  2650.         this.mClosedWindowName = null;
  2651.  
  2652.         // Cleanup left over files from Crash Recovery
  2653.         if (this.getPref("extensions.crashrecovery.resume_session_once", false, true))
  2654.         {    
  2655.             this.delFile(this.getProfileFile("crashrecovery.dat"), true);
  2656.             this.delFile(this.getProfileFile("crashrecovery.bak"), true);
  2657.             this.delPref("extensions.crashrecovery.resume_session_once", true);
  2658.         }
  2659.         this.setRunning(false);
  2660.         this.log("Shutdown end", "TRACE");
  2661.     },
  2662.     
  2663.     autoSaveCurrentSession: function(aForceSave)
  2664.     {
  2665.         try
  2666.         {
  2667.             if (aForceSave || !this.isPrivateBrowserMode()) {
  2668.                 var state = this.getSessionState(this._string("autosave_session"), null, null, null, (this._string_backup_sessions || this._string("backup_sessions")));
  2669.                 if (!state) return;
  2670.                 this.writeFile(this.getSessionDir(this.mAutoSaveSessionName), state);
  2671.             }
  2672.         }
  2673.         catch (ex)
  2674.         {
  2675.             this.ioError(ex);
  2676.         }
  2677.     },
  2678.  
  2679.     backupCurrentSession: function(aEnteringPrivateBrowsingMode)
  2680.     {
  2681.         this.log("backupCurrentSession start", "TRACE");
  2682.         var backup = this.mPref_backup_session;
  2683.         var temp_backup = (this.mPref_startup > 0) && (this.mPref_resume_session == this.mBackupSessionName);
  2684.  
  2685.         this.log("backupCurrentSession: backup = " + backup + ", temp_backup = " + temp_backup, "DATA");
  2686.  
  2687.         // Get results from prompt in component if it was displayed and set the value back to the default
  2688.         var results = this.mApplication.storage.get(this.mShutdownPromptResults, -1);
  2689.         this.log("backupCurrentSession: results = " + results, "DATA");
  2690.         if (results != -1) this.mApplication.storage.set(this.mShutdownPromptResults, -1);
  2691.         
  2692.         // If quit was pressed, skip all the session stuff below
  2693.         if (results == 1) backup = -1;
  2694.         
  2695.         // Don't save if just a blank window, if there's an error parsing data, just save
  2696.         var state = null;
  2697.         if ((backup > 0) || temp_backup) {
  2698.             // If shut down in private browsing mode, use the pre-private sesssion, otherwise get the current one
  2699.             var helper_state = (this.mShutDownInPrivateBrowsingMode || this.isPrivateBrowserMode()) ? this.mSMHelper.mBackupState : null;
  2700.             this.log("backupCurrentSession: helper_state = " + helper_state, "DATA");
  2701.         
  2702.             try {
  2703.                 state = this.getSessionState(this._string_backup_session || this._string("backup_session"), null, this.getNoUndoData(), null, (this._string_backup_sessions || this._string("backup_sessions")), true, null, helper_state);
  2704.             } catch(ex) {
  2705.                 this.logError(ex);
  2706.             }
  2707.             try {
  2708.                 var aState = this.JSON_decode(state.split("\n")[4]);
  2709.                 this.log("backupCurrentSession: Number of Windows #1 = " + aState.windows.length, "DATA");
  2710.                 this.log(state, "STATE");
  2711.                 // if window data has been cleared ("Visited Pages" cleared on shutdown), use mClosingWindowState, if it exists.
  2712.                 if (aState.windows.length == 0 && this.mClosingWindowState) {
  2713.                     this.log("backupCurrentSession: Using closing Window State", "INFO");
  2714.                     state = this.getSessionState(this._string_backup_session || this._string("backup_session"), null, this.getNoUndoData(), null, (this._string_backup_sessions || this._string("backup_sessions")), true, null, this.mClosingWindowState);
  2715.                     this.log(state, "STATE");
  2716.                     aState = this.JSON_decode(state.split("\n")[4]);
  2717.                 }
  2718.                 this.log("backupCurrentSession: Number of Windows #2 = " + aState.windows.length, "DATA");
  2719.                 if (!((aState.windows.length > 1) || (aState.windows[0]._closedTabs.length > 0) || (aState.windows[0].tabs.length > 1) || 
  2720.                     (aState.windows[0].tabs[0].entries.length > 1) || 
  2721.                     ((aState.windows[0].tabs[0].entries.length == 1 && aState.windows[0].tabs[0].entries[0].url != "about:blank")))) {
  2722.                     backup = 0;
  2723.                     temp_backup = false;
  2724.                 }
  2725.             } catch(ex) { 
  2726.                 this.logError(ex);
  2727.             }
  2728.         }
  2729.  
  2730.         if (backup == 2)
  2731.         {
  2732.             var dontPrompt = { value: false };
  2733.             if (results == -1) {
  2734.                 var saveRestore = !(this.getPref("browser.sessionstore.resume_session_once", false, true) || this.doResumeCurrent() || aEnteringPrivateBrowsingMode);
  2735.                 var flags = this.mPromptService.BUTTON_TITLE_SAVE * this.mPromptService.BUTTON_POS_0 + 
  2736.                             this.mPromptService.BUTTON_TITLE_DONT_SAVE * this.mPromptService.BUTTON_POS_1 + 
  2737.                             (saveRestore ? (this.mPromptService.BUTTON_TITLE_IS_STRING * this.mPromptService.BUTTON_POS_2) : 0); 
  2738.                 results = this.mPromptService.confirmEx(null, this.mTitle, this._string_preserve_session || this._string("preserve_session"), flags,
  2739.                           null, null, this._string_save_and_restore || this._string("save_and_restore"),
  2740.                           this._string_prompt_not_again || this._string("prompt_not_again"), dontPrompt);
  2741.             }
  2742.             backup = (results == 1)?-1:1;
  2743.             if (results == 2) {
  2744.                 if (dontPrompt.value) {
  2745.                     this.setPref("resume_session", this.mBackupSessionName);
  2746.                     this.setPref("startup", 2);
  2747.                 }
  2748.                 else this.setPref("restore_temporary", true);
  2749.             }
  2750.             if (dontPrompt.value)
  2751.             {
  2752.                 this.setPref("backup_session", (backup == -1)?0:1);
  2753.             }
  2754.         }
  2755.         if (backup > 0 || temp_backup)
  2756.         {
  2757.             this.keepOldBackups(backup > 0);
  2758.             
  2759.             // encrypt state if encryption preference set
  2760.             if (this.mPref_encrypt_sessions) {
  2761.                 state = state.split("\n")
  2762.                 state[4] = this.decryptEncryptByPreference(state[4]);
  2763.                 if (!state[4]) return;
  2764.                 state = state.join("\n");
  2765.             }
  2766.             
  2767.             try
  2768.             {
  2769.                 this.writeFile(this.getSessionDir(this.mBackupSessionName), state);
  2770.                 if (temp_backup && (backup <= 0)) this.setPref("backup_temporary", true);
  2771.             }
  2772.             catch (ex)
  2773.             {
  2774.                 this.ioError(ex);
  2775.             }
  2776.         }
  2777.         else this.keepOldBackups(false);
  2778.         this.log("backupCurrentSession end", "TRACE");
  2779.     },
  2780.  
  2781.     keepOldBackups: function(backingUp)
  2782.     {
  2783.         if (!backingUp) this.mPref_max_backup_keep = this.mPref_max_backup_keep + 1; 
  2784.         var backup = this.getSessionDir(this.mBackupSessionName);
  2785.         if (backup.exists() && this.mPref_max_backup_keep)
  2786.         {
  2787.             var oldBackup = this.getSessionDir(this.mBackupSessionName, true);
  2788.             // preserve date that file was backed up
  2789.             var date = new Date();
  2790.             date.setTime(backup.lastModifiedTime); 
  2791.             var name = this.getFormattedName("", date, this._string_old_backup_session || this._string("old_backup_session"));
  2792.             this.writeFile(oldBackup, this.nameState(this.readSessionFile(backup), name));
  2793.             this.delFile(backup, true);
  2794.         }
  2795.         
  2796.         if (this.mPref_max_backup_keep != -1)
  2797.         {
  2798.             this.getSessions().filter(function(aSession) {
  2799.                 return /^backup-\d+\.session$/.test(aSession.fileName);
  2800.             }).sort(function(a, b) {
  2801.                 return b.timestamp - a.timestamp;
  2802.             }).slice(this.mPref_max_backup_keep).forEach(function(aSession) {
  2803.                 this.delFile(this.getSessionDir(aSession.fileName), true);
  2804.             }, this);
  2805.         }
  2806.     },
  2807.  
  2808.     readSessionFile: function(aFile,headerOnly)
  2809.     {
  2810.         function getCountString(aCount) { 
  2811.             return "\tcount=" + aCount.windows + "/" + aCount.tabs + "\n"; 
  2812.         };
  2813.  
  2814.         var state = this.readFile(aFile,headerOnly);
  2815.         
  2816.         // old crashrecovery file format
  2817.         if ((/\n\[Window1\]\n/.test(state)) && 
  2818.             (/^\[SessionManager\]\n(?:name=(.*)\n)?(?:timestamp=(\d+))?/m.test(state))) 
  2819.         {
  2820.             // read entire file if only read header
  2821.             var name = RegExp.$1 || this._string("untitled_window");
  2822.             var timestamp = parseInt(RegExp.$2) || aFile.lastModifiedTime;
  2823.             if (headerOnly) state = this.readFile(aFile);
  2824.             state = state.substring(state.indexOf("[Window1]\n"), state.length);
  2825.             state = this.JSON_encode(this.decodeOldFormat(state, true));
  2826.             var countString = getCountString(this.getCount(state));
  2827.             state = "[SessionManager v2]\nname=" + name + "\ntimestamp=" + timestamp + "\nautosave=false" + countString + state;
  2828.             this.writeFile(aFile, state);
  2829.         }
  2830.         // Not latest session format
  2831.         else if ((/^\[SessionManager( v2)?\]\nname=.*\ntimestamp=\d+\n/m.test(state)) && (!this.mSessionRegExp.test(state)))
  2832.         {
  2833.             // This should always match, but is required to get the RegExp values set correctly.
  2834.             // matchArray[0] - Entire 4 line header
  2835.             // matchArray[1] - Top 3 lines (includes name and timestamp)
  2836.             // matchArray[2] - " v2" (if it exists) - if missing file is in old format
  2837.             // matchArray[3] - Autosave string (if it exists)
  2838.             // matchArray[4] - Autosave value (not really used at the moment)
  2839.             // matchArray[5] - Count string (if it exists)
  2840.             // matchArray[6] - Group string and any invalid count string before (if either exists)
  2841.             // matchArray[7] - Invalid count string (if it exists)
  2842.             // matchArray[8] - Group string (if it exists)
  2843.             // matchArray[9] - Screen size string and, if no group string, any invalid count string before (if either exists)
  2844.             // matchArray[10] - Invalid count string (if it exists)
  2845.             // matchArray[11] - Screen size string (if it exists)
  2846.             var matchArray = /(^\[SessionManager( v2)?\]\nname=.*\ntimestamp=\d+\n)(autosave=(false|true|session\/?\d*|window\/?\d*)[\n]?)?(\tcount=[1-9][0-9]*\/[1-9][0-9]*[\n]?)?((\t.*)?(\tgroup=[^\t|^\n|^\r]+[\n]?))?((\t.*)?(\tscreensize=\d+x\d+[\n]?))?/m.exec(state)
  2847.             if (matchArray)
  2848.             {    
  2849.                 // If two autosave lines, session file is bad so try and fix it (shouldn't happen anymore)
  2850.                 var goodSession = !/autosave=(false|true|session\/?\d*|window\/?\d*).*\nautosave=(false|true|session\/?\d*|window\/?\d*)/m.test(state);
  2851.                 
  2852.                 // read entire file if only read header
  2853.                 if (headerOnly) state = this.readFile(aFile);
  2854.  
  2855.                 if (goodSession)
  2856.                 {
  2857.                     var data = state.split("\n")[((matchArray[3]) ? 4 : 3)];
  2858.                     var backup_data = data;
  2859.                     // decrypt if encrypted, do not decode if in old format since old format was not encoded
  2860.                     data = this.decrypt(data, true, !matchArray[2]);
  2861.                     // If old format test JSON data
  2862.                     if (!matchArray[2]) {
  2863.                         matchArray[1] = matchArray[1].replace(/^\[SessionManager\]/, "[SessionManager v2]");
  2864.                         var test_decode = this.JSON_decode(data, true);
  2865.                         // if it failed to decode, try to decrypt again using new format
  2866.                         if (test_decode._JSON_decode_failed) {
  2867.                             data = this.decrypt(backup_data, true);
  2868.                         }
  2869.                     }
  2870.                     backup_data = null;
  2871.                     if (!data) {
  2872.                         // master password entered, but still could not be decrypted - either corrupt or saved under different profile
  2873.                         if (data == false) {
  2874.                             this.moveToCorruptFolder(aFile);
  2875.                         }
  2876.                         return null;
  2877.                     }
  2878.                     var countString = (matchArray[5]) ? (matchArray[5]) : getCountString(this.getCount(data));
  2879.                     // remove \n from count string if group or screen size is there
  2880.                     if ((matchArray[8] || matchArray[11]) && (countString[countString.length-1] == "\n")) countString = countString.substring(0, countString.length - 1);
  2881.                     var autoSaveString = (matchArray[3]) ? (matchArray[3]).split("\n")[0] : "autosave=false";
  2882.                     if (autoSaveString == "autosave=true") autoSaveString = "autosave=session/";
  2883.                     state = matchArray[1] + autoSaveString + countString + (matchArray[8] ? matchArray[8] : "") + (matchArray[11] ? matchArray[11] : "") + this.decryptEncryptByPreference(data);
  2884.                     // bad session so rename it so it won't load again - This catches case where window and/or 
  2885.                     // tab count is zero.  Technically we can load when tab count is 0, but that should never
  2886.                     // happen so session is probably corrupted anyway so just flag it so.
  2887.                     if (/(\d\/0)|(0\/\d)/.test(countString)) 
  2888.                     {
  2889.                         // If one window and no tabs (blank session), delete file otherwise mark it bad
  2890.                         if (countString == "\tcount=1/0\n") {
  2891.                             this.delFile(aFile, true);
  2892.                             return null;
  2893.                         }
  2894.                         else {
  2895.                             this.moveToCorruptFolder(aFile);
  2896.                             return null;
  2897.                         }
  2898.                     }
  2899.                     this.writeFile(aFile, state);
  2900.                 }
  2901.                 // else bad session format, attempt to recover by removing extra line
  2902.                 else {
  2903.                     var newstate = state.split("\n");
  2904.                     newstate.splice(3,newstate.length - (newstate[newstate.length-1].length ? 5 : 6));
  2905.                     if (RegExp.$6 == "\tcount=0/0") newstate.splice(3,1);
  2906.                     state = newstate.join("\n");
  2907.                     this.writeFile(aFile, state);
  2908.                     state = this.readSessionFile(aFile,headerOnly);
  2909.                 }
  2910.             }
  2911.         }
  2912.         
  2913.         return state;
  2914.     },
  2915.     
  2916.     readFile: function(aFile,headerOnly)
  2917.     {
  2918.         try
  2919.         {
  2920.             var stream = this.mComponents.classes["@mozilla.org/network/file-input-stream;1"].createInstance(this.mComponents.interfaces.nsIFileInputStream);
  2921.             stream.init(aFile, 0x01, 0, 0);
  2922.             var cvstream = this.mComponents.classes["@mozilla.org/intl/converter-input-stream;1"].createInstance(this.mComponents.interfaces.nsIConverterInputStream);
  2923.             cvstream.init(stream, "UTF-8", 1024, this.mComponents.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  2924.             
  2925.             var content = "";
  2926.             var data = {};
  2927.             while (cvstream.readString(4096, data))
  2928.             {
  2929.                 content += data.value;
  2930.                 if (headerOnly) break;
  2931.             }
  2932.             cvstream.close();
  2933.             
  2934.             return content.replace(/\r\n?/g, "\n");
  2935.         }
  2936.         catch (ex) { }
  2937.         
  2938.         return null;
  2939.     },
  2940.  
  2941.     writeFile: function(aFile, aData)
  2942.     {
  2943.         if (!aData) return;  // this handles case where data could not be encrypted and null was passed to writeFile
  2944.         var stream = this.mComponents.classes["@mozilla.org/network/file-output-stream;1"].createInstance(this.mComponents.interfaces.nsIFileOutputStream);
  2945.         stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
  2946.         var cvstream = this.mComponents.classes["@mozilla.org/intl/converter-output-stream;1"].createInstance(this.mComponents.interfaces.nsIConverterOutputStream);
  2947.         cvstream.init(stream, "UTF-8", 0, this.mComponents.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
  2948.         
  2949.         cvstream.writeString(aData.replace(/\n/g, this.mEOL));
  2950.         cvstream.flush();
  2951.         cvstream.close();
  2952.     },
  2953.  
  2954.     delFile: function(aFile, aSilent)
  2955.     {
  2956.         if (aFile && aFile.exists())
  2957.         {
  2958.             try
  2959.             {
  2960.                 aFile.remove(false);
  2961.             }
  2962.             catch (ex)
  2963.             {
  2964.                 if (!aSilent)
  2965.                 {
  2966.                     this.ioError(ex);
  2967.                 }
  2968.             }
  2969.         }
  2970.     },
  2971.     
  2972.     moveToCorruptFolder: function(aFile, aSilent)
  2973.     {
  2974.         try {
  2975.             if (aFile.exists()) 
  2976.             {
  2977.                 var dir = this.getSessionDir();
  2978.                 dir.append("Corrupt_Sessions");
  2979.         
  2980.                 if (!dir.exists()) {
  2981.                     dir.create(this.mComponents.interfaces.nsIFile.DIRECTORY_TYPE, 0700);
  2982.                 }
  2983.         
  2984.                 aFile.moveTo(dir, null);
  2985.             }
  2986.         }    
  2987.         catch (ex) { 
  2988.             if (!aSilent) this.ioError(ex); 
  2989.         }
  2990.     },
  2991.  
  2992. /* ........ Encryption functions .............. */
  2993.  
  2994.     cryptError: function(aException, notSaved)
  2995.     {
  2996.         var text;
  2997.         if (aException.message) {
  2998.             if (aException.message.indexOf("decryptString") != -1) {
  2999.                 if (aException.name != "NS_ERROR_NOT_AVAILABLE") {
  3000.                     text = this._string("decrypt_fail1");
  3001.                 }
  3002.                 else {
  3003.                     text = this._string("decrypt_fail2");
  3004.                 }
  3005.             }
  3006.             else {
  3007.                 text = notSaved ? (this._string_encrypt_fail2 || this._string("encrypt_fail2")) : (this._string_encrypt_fail || this._string("encrypt_fail"));
  3008.             }
  3009.         }
  3010.         else text = aException;
  3011.         this.mPromptService.alert((this.mBundle)?window:null, this.mTitle, text);
  3012.     },
  3013.  
  3014.     decrypt: function(aData, aNoError, doNotDecode)
  3015.     {
  3016.         // Encrypted data is in BASE64 format so ":" won't be in encrypted data, but is in session data.
  3017.         // The encryptString function cannot handle non-ASCII data so encode it first and decode the results
  3018.         if (aData.indexOf(":") == -1)
  3019.         {
  3020.             try {
  3021.                 aData = this.mSecretDecoderRing.decryptString(aData);
  3022.                 if (!doNotDecode) aData = decodeURIComponent(aData);
  3023.             }
  3024.             catch (ex) { 
  3025.                 if (!aNoError) this.cryptError(ex); 
  3026.                 // encrypted file corrupt, return false so as to not break things checking for aData.
  3027.                 if (ex.name != "NS_ERROR_NOT_AVAILABLE") { 
  3028.                     return false;
  3029.                 }
  3030.                 return null;
  3031.             }
  3032.         }
  3033.         return aData;
  3034.     },
  3035.  
  3036.     // This function will encrypt the data if the encryption preference is set.
  3037.     // It will also decrypt encrypted data if the encryption preference is not set.
  3038.     decryptEncryptByPreference: function(aData)
  3039.     {
  3040.         // Encrypted data is in BASE64 format so ":" won't be in encrypted data, but is in session data.
  3041.         // The encryptString function cannot handle non-ASCII data so encode it first and decode the results
  3042.         var encrypted = (aData.indexOf(":") == -1);
  3043.         try {
  3044.             if (this.mPref_encrypt_sessions && !encrypted)
  3045.             {
  3046.                 aData = this.mSecretDecoderRing.encryptString(encodeURIComponent(aData));
  3047.             }
  3048.             else if (!this.mPref_encrypt_sessions && encrypted)
  3049.             {
  3050.                 aData = decodeURIComponent(this.mSecretDecoderRing.decryptString(aData));
  3051.             }
  3052.         }
  3053.         catch (ex) { 
  3054.             if (!encrypted && this.mPref_encrypted_only) {
  3055.                 this.cryptError(ex, true);
  3056.                 return null;
  3057.             }
  3058.             else this.cryptError(ex);
  3059.         }
  3060.         return aData;
  3061.     },
  3062.  
  3063.     encryptionChange: function()
  3064.     {
  3065.         try {
  3066.             // force a master password prompt so we don't waste time if user cancels it
  3067.             this.mSecretDecoderRing.encryptString("");
  3068.             
  3069.             var sessions = this.getSessions();
  3070.             sessions.forEach(function(aSession) {
  3071.                 var file = this.getSessionDir(aSession.fileName);
  3072.                 var state = this.readSessionFile(file);
  3073.                 if (state) 
  3074.                 {
  3075.                     if (this.mSessionRegExp.test(state))
  3076.                     {
  3077.                         state = state.split("\n")
  3078.                         state[4] = this.decryptEncryptByPreference(state[4]);
  3079.                         state = state.join("\n");
  3080.                         this.writeFile(file, state);
  3081.                     }
  3082.                 }
  3083.             }, this);
  3084.         
  3085.             if (!this.mUseSSClosedWindowList) {
  3086.                 var windows = this.getClosedWindows_SM();
  3087.                 windows.forEach(function(aWindow) {
  3088.                     aWindow.state = this.decryptEncryptByPreference(aWindow.state);
  3089.                 }, this);
  3090.                 this.storeClosedWindows_SM(windows);
  3091.             }
  3092.         }
  3093.         // failed to encrypt/decrypt so revert setting
  3094.         catch (ex) {
  3095.             this.setPref("encrypt_sessions",!this.mPref_encrypt_sessions);
  3096.             this.cryptError(this._string("change_encryption_fail"));
  3097.         }
  3098.     },
  3099.  
  3100. /* ........ Conversion functions .............. */
  3101.  
  3102.     decodeOldFormat: function(aIniString, moveClosedTabs)
  3103.     {
  3104.         var rootObject = {};
  3105.         var obj = rootObject;
  3106.         var lines = aIniString.split("\n");
  3107.     
  3108.         for (var i = 0; i < lines.length; i++)
  3109.         {
  3110.             try
  3111.             {
  3112.                 if (lines[i].charAt(0) == "[")
  3113.                 {
  3114.                     obj = this.ini_getObjForHeader(rootObject, lines[i]);
  3115.                 }
  3116.                 else if (lines[i] && lines[i].charAt(0) != ";")
  3117.                 {
  3118.                     this.ini_setValueForLine(obj, lines[i]);
  3119.                 }
  3120.             }
  3121.             catch (ex)
  3122.             {
  3123.                 throw new Error("Error at line " + (i + 1) + ": " + ex.description);
  3124.             }
  3125.         }
  3126.     
  3127.         // move the closed tabs to the right spot
  3128.         if (moveClosedTabs == true)
  3129.         {
  3130.             try
  3131.             {
  3132.                 rootObject.windows.forEach(function(aValue, aIndex) {
  3133.                     if (aValue.tabs && aValue.tabs[0]._closedTabs)
  3134.                     {
  3135.                         aValue["_closedTabs"] = aValue.tabs[0]._closedTabs;
  3136.                         delete aValue.tabs[0]._closedTabs;
  3137.                     }
  3138.                 }, this);
  3139.             }
  3140.             catch (ex) {}
  3141.         }
  3142.     
  3143.         return rootObject;
  3144.     },
  3145.  
  3146.     ini_getObjForHeader: function(aObj, aLine)
  3147.     {
  3148.         var names = aLine.split("]")[0].substr(1).split(".");
  3149.     
  3150.         for (var i = 0; i < names.length; i++)
  3151.         {
  3152.             if (!names[i])
  3153.             {
  3154.                 throw new Error("Invalid header: [" + names.join(".") + "]!");
  3155.             }
  3156.             if (/(\d+)$/.test(names[i]))
  3157.             {
  3158.                 names[i] = names[i].slice(0, -RegExp.$1.length);
  3159.                 var ix = parseInt(RegExp.$1) - 1;
  3160.                 names[i] = this.ini_fixName(names[i]);
  3161.                 aObj = aObj[names[i]] = aObj[names[i]] || [];
  3162.                 aObj = aObj[ix] = aObj[ix] || {};
  3163.             }
  3164.             else
  3165.             {
  3166.                 names[i] = this.ini_fixName(names[i]);
  3167.                 aObj = aObj[names[i]] = aObj[names[i]] || {};
  3168.             }
  3169.         }
  3170.     
  3171.         return aObj;
  3172.     },
  3173.  
  3174.     ini_setValueForLine: function(aObj, aLine)
  3175.     {
  3176.         var ix = aLine.indexOf("=");
  3177.         if (ix < 1)
  3178.         {
  3179.             throw new Error("Invalid entry: " + aLine + "!");
  3180.         }
  3181.     
  3182.         var value = aLine.substr(ix + 1);
  3183.         if (value == "true" || value == "false")
  3184.         {
  3185.             value = (value == "true");
  3186.         }
  3187.         else if (/^\d+$/.test(value))
  3188.         {
  3189.             value = parseInt(value);
  3190.         }
  3191.         else if (value.indexOf("%") > -1)
  3192.         {
  3193.             value = decodeURI(value.replace(/%3B/gi, ";"));
  3194.         }
  3195.         
  3196.         var name = this.ini_fixName(aLine.substr(0, ix));
  3197.         if (name == "xultab")
  3198.         {
  3199.             //this.ini_parseCloseTabList(aObj, value);
  3200.         }
  3201.         else
  3202.         {
  3203.             aObj[name] = value;
  3204.         }
  3205.     },
  3206.  
  3207.     // This results in some kind of closed tab data being restored, but it is incomplete
  3208.     // as all closed tabs show up as "undefined" and they don't restore.  If someone
  3209.     // can fix this feel free, but since it is basically only used once I'm not going to bother.
  3210.     ini_parseCloseTabList: function(aObj, aCloseTabData)
  3211.     {
  3212.         var ClosedTabObject = {};
  3213.         var ix = aCloseTabData.indexOf("=");
  3214.         if (ix < 1)
  3215.         {
  3216.             throw new Error("Invalid entry: " + aCloseTabData + "!");
  3217.         }
  3218.         var serializedTabs = aCloseTabData.substr(ix + 1);
  3219.         serializedTabs = decodeURI(serializedTabs.replace(/%3B/gi, ";"));
  3220.         var closedTabs = serializedTabs.split("\f\f").map(function(aData) {
  3221.             if (/^(\d+) (.*)\n([\s\S]*)/.test(aData))
  3222.             {
  3223.                 return { name: RegExp.$2, pos: parseInt(RegExp.$1), state: RegExp.$3 };
  3224.             }
  3225.             return null;
  3226.         }).filter(function(aTab) { return aTab != null; }).slice(0, this.getPref("browser.sessionstore.max_tabs_undo", 10, true));
  3227.  
  3228.         closedTabs.forEach(function(aValue, aIndex) {
  3229.             closedTabs[aIndex] = this.decodeOldFormat(aValue.state, false)
  3230.             closedTabs[aIndex] = closedTabs[aIndex].windows;
  3231.             closedTabs[aIndex] = closedTabs[aIndex][0].tabs;
  3232.         }, this);
  3233.  
  3234.         aObj["_closedTabs"] = [];
  3235.  
  3236.         closedTabs.forEach(function(aValue, aIndex) {
  3237.             aObj["_closedTabs"][aIndex] = this.JSON_decode({ state : this.JSON_encode(aValue[0]) });
  3238.         }, this);
  3239.     },
  3240.  
  3241.     ini_fixName: function(aName)
  3242.     {
  3243.         switch (aName)
  3244.         {
  3245.             case "Window":
  3246.                 return "windows";
  3247.             case "Tab":
  3248.                 return "tabs";
  3249.             case "Entry":
  3250.                 return "entries";
  3251.             case "Child":
  3252.                 return "children";
  3253.             case "Cookies":
  3254.                 return "cookies";
  3255.             case "uri":
  3256.                 return "url";
  3257.             default:
  3258.                 return aName;
  3259.         }            
  3260.     },
  3261.  
  3262. /* ........ Preference Access .............. */
  3263.  
  3264.     // Certain preferences should be force saved in case of a crash
  3265.     checkForForceSave: function(aName, aValue, aUseRootBranch)
  3266.     {
  3267.         var names = [ "_autosave_values" ];
  3268.         
  3269.         for (var i=0; i<names.length; i++) {
  3270.             if (aName == names[i]) {
  3271.                 var currentValue = this.getPref(aName, null, aUseRootBranch);
  3272.                 return (currentValue != aValue);
  3273.             }
  3274.         }
  3275.         return false;
  3276.     },
  3277.         
  3278.  
  3279.     getPref: function(aName, aDefault, aUseRootBranch)
  3280.     {
  3281.         try
  3282.         {
  3283.             var pb = (aUseRootBranch)?this.mPrefRoot:this.mPrefBranch;
  3284.             switch (pb.getPrefType(aName))
  3285.             {
  3286.                 case pb.PREF_STRING:
  3287.                     //return pb.getCharPref(aName);
  3288.                     // handle unicode values
  3289.                     return pb.getComplexValue(aName,this.mComponents.interfaces.nsISupportsString).data
  3290.                 case pb.PREF_BOOL:
  3291.                     return pb.getBoolPref(aName);
  3292.                 case pb.PREF_INT:
  3293.                     return pb.getIntPref(aName);
  3294.             }
  3295.         }
  3296.         catch (ex) { }
  3297.         
  3298.         return aDefault;
  3299.     },
  3300.  
  3301.     setPref: function(aName, aValue, aUseRootBranch)
  3302.     {
  3303.         var forceSave = this.checkForForceSave(aName, aValue, aUseRootBranch);
  3304.         
  3305.         var pb = (aUseRootBranch)?this.mPrefRoot:this.mPrefBranch;
  3306.         switch (typeof aValue)
  3307.         {
  3308.         case "boolean":
  3309.             pb.setBoolPref(aName, aValue);
  3310.             break;
  3311.         case "number":
  3312.             pb.setIntPref(aName, parseInt(aValue));
  3313.             break;
  3314.         default:
  3315.             //pb.setCharPref(aName, "" + aValue);
  3316.             // Handle unicode preferences
  3317.             var str = this.mComponents.classes["@mozilla.org/supports-string;1"].createInstance(this.mComponents.interfaces.nsISupportsString);
  3318.             str.data = aValue;
  3319.             pb.setComplexValue(aName,this.mComponents.interfaces.nsISupportsString, str);
  3320.  
  3321.             break;
  3322.         }
  3323.         
  3324.         if (forceSave) this.mObserverService.notifyObservers(null,"sessionmanager-preference-save",null);
  3325.     },
  3326.  
  3327.     delPref: function(aName, aUseRootBranch)
  3328.     {
  3329.         ((aUseRootBranch)?this.mPrefRoot:this.mPrefBranch).deleteBranch(aName);
  3330.     },
  3331.  
  3332. /* ........ Miscellaneous Enhancements .............. */
  3333.  
  3334.     // Check for Running
  3335.     isRunning: function() {
  3336.         return this.mApplication.storage.get("sessionmanager._running", false);
  3337.     },
  3338.     
  3339.     // Check for Running
  3340.     setRunning: function(aValue) {
  3341.         return this.mApplication.storage.set("sessionmanager._running", aValue);
  3342.     },
  3343.  
  3344.     // Caching functions
  3345.     getSessionCache: function(aName) {
  3346.         return this.mApplication.storage.get(this.mSessionCache + aName, null);
  3347.     },
  3348.     
  3349.     setSessionCache: function(aName, aData) {
  3350.         this.mApplication.storage.set(this.mSessionCache + aName, aData);
  3351.     },
  3352.     
  3353.     getClosedWindowCache: function(aData, aLengthOnly) {
  3354.         if (aData && aLengthOnly) {
  3355.             return this.mApplication.storage.get(this.mClosedWindowsCacheLength, 0);
  3356.         }
  3357.         else if (aData) {
  3358.             return this.mApplication.storage.get(this.mClosedWindowsCacheData, null);
  3359.         }
  3360.         else {
  3361.             return this.mApplication.storage.get(this.mClosedWindowsCacheTimestamp, 0);
  3362.         }
  3363.     },
  3364.  
  3365.     setClosedWindowCache: function(aData, aTimestamp) {
  3366.         this.mApplication.storage.set(this.mClosedWindowsCacheData, aData);
  3367.         this.mApplication.storage.set(this.mClosedWindowsCacheTimestamp, (aData ? aTimestamp : 0));
  3368.         this.mApplication.storage.set(this.mClosedWindowsCacheLength, (aData ? aData.split("\n\n").length : 0));
  3369.     },
  3370.     
  3371.     // Read Autosave values from preference and store into global variables
  3372.     getAutoSaveValues: function(aValues, aOneWindow)
  3373.     {
  3374.         if (!aValues) aValues = "";
  3375.         this.log("getAutoSaveValues: aOneWindow = " + aOneWindow + ", aValues = " + aValues.split("\n").join(", "), "EXTRA");
  3376.         var values = aValues.split("\n");
  3377.         if (aOneWindow) {
  3378.             var old_window_session_name = this.__window_session_name;
  3379.             this.__window_session_name = values[0];
  3380.             this.__window_session_group = values[1];
  3381.             this.__window_session_time = (!values[2] || isNaN(values[2])) ? 0 : values[2];
  3382.             try {
  3383.                 var windowSessions = this.mApplication.storage.get(this.mActiveWindowSessions, {});
  3384.                 // This throws whenever a window is already closed (during shutdown for example) or if the value doesn't exist and we try to delete it
  3385.                 if (aValues) {
  3386.                     // Store window session into Application storage and set window value
  3387.                     windowSessions[values[0].trim().toLowerCase()] = true;
  3388.                     this.mApplication.storage.set(this.mActiveWindowSessions, windowSessions);
  3389.                     this.mSessionStore.setWindowValue(window, "_sm_window_session_values", aValues);
  3390.                 }
  3391.                 else {
  3392.                     if (old_window_session_name) {
  3393.                         // Remove window session from Application storage and delete window value
  3394.                         delete windowSessions[old_window_session_name.trim().toLowerCase()];
  3395.                         this.mApplication.storage.set(this.mActiveWindowSessions, windowSessions);
  3396.                     }
  3397.                     this.mSessionStore.deleteWindowValue(window, "_sm_window_session_values");
  3398.                     
  3399.                     // the following forces SessionStore to save the state to disk (bug 510965)
  3400.                     // Can't just set _sm_window_session_values to "" and then delete since that will throw an exception
  3401.                     this.mSessionStore.setWindowValue(window, "SM_dummy_value","1");
  3402.                     this.mSessionStore.deleteWindowValue(window, "SM_dummy_value");
  3403.                 }
  3404.             }
  3405.             catch(ex) {
  3406.                 // log it so we can tell when things aren't working
  3407.                 this.logError(ex);
  3408.             }
  3409.             
  3410.             // start/stop window timer
  3411.             this.checkWinTimer();
  3412.             gBrowser.updateTitlebar();
  3413.         }
  3414.         else {
  3415.             this.mPref__autosave_name = values[0];
  3416.             this.mPref__autosave_group = values[1];
  3417.             this.mPref__autosave_time = (!values[2] || isNaN(values[2])) ? 0 : values[2];
  3418.         }
  3419.     },
  3420.  
  3421.     // Merge autosave variables into a a string
  3422.     mergeAutoSaveValues: function(name, group, time)
  3423.     {
  3424.         var values = [ name, group, time ];
  3425.         return values.join("\n");
  3426.     },
  3427.     
  3428.     // Bug 374288 causes all elements that don't have a specified tooltip or tooltiptext to inherit their
  3429.     // ancestors tooltip/tooltiptext.  To work around this set a blank tooltiptext for all descendents of aNode.
  3430.     //
  3431.     fixBug374288: function(aNode)
  3432.     {
  3433.         if (aNode && aNode.childNodes) {
  3434.             for (var i in aNode.childNodes) {
  3435.                 var child = aNode.childNodes[i];
  3436.                 if (child && child.getAttribute && !child.getAttribute("tooltiptext")) {
  3437.                     child.setAttribute("tooltiptext", "");
  3438.                 }
  3439.                 this.fixBug374288(child);
  3440.             }
  3441.         }
  3442.     },
  3443.  
  3444.     // Called to handle clearing of private data (stored sessions) when the toolbar item is selected
  3445.     // and when the clear now button is pressed in the privacy options pane.  If the option to promptOnSanitize
  3446.     // is set, this function ignores the request and let's the Firefox Sanitize function call
  3447.     // gSessionManager.santize when Clear Private Data okay button is pressed and Session Manager's checkbox
  3448.     // is selected.
  3449.     tryToSanitize: function()
  3450.     {
  3451.         // User disabled the prompt before clear option and session manager is checked in the privacy data settings
  3452.         if ( !this.getPref("privacy.sanitize.promptOnSanitize", true, true) &&
  3453.              this.getPref("privacy.item.extensions-sessionmanager", false, true) ) 
  3454.         {
  3455.             this.sanitize();
  3456.             return true;
  3457.         }
  3458.     
  3459.         return false;
  3460.     },
  3461.         
  3462.     recoverSession: function()
  3463.     {
  3464.         var file, temp_restore = null, first_temp_restore = null, temp_restore_index = 1;
  3465.         var recovering = this.getPref("_recovering");
  3466.         // Use SessionStart's value in FF3 because preference is cleared by the time we are called
  3467.         var sessionstart = (this.mSessionStartup.sessionType != Components.interfaces.nsISessionStartup.NO_SESSION) && !this.mApplication.storage.get(this.mAlreadyShutdown, false);
  3468.         var recoverOnly = this.isRunning() || sessionstart || this.getPref("_no_prompt_for_session", false);
  3469.         this.delPref("_no_prompt_for_session");
  3470.         this.log("recoverSession: recovering = " + recovering + ", sessionstart = " + sessionstart + ", recoverOnly = " + recoverOnly, "DATA");
  3471.         if (typeof(this._temp_restore) == "string") {
  3472.             this.log("recoverSession: command line session data = \"" + this._temp_restore + "\"", "DATA");
  3473.             temp_restore = this._temp_restore.split("\n");
  3474.             first_temp_restore = temp_restore[1];
  3475.         }
  3476.         this._temp_restore = null;
  3477.  
  3478.         // handle crash where user chose a specific session
  3479.         if (recovering)
  3480.         {
  3481.             var choseTabs = false;
  3482.             choseTabs = this.getPref("_chose_tabs");
  3483.             this.delPref("_recovering");
  3484.             this.delPref("_chose_tabs"); // delete chose tabs preference if set
  3485.             this.load(recovering, "startup", choseTabs);
  3486.         }
  3487.         else if (!recoverOnly && (this.mPref_restore_temporary || first_temp_restore || (this.mPref_startup == 1) || ((this.mPref_startup == 2) && this.mPref_resume_session)) && this.getSessions().length > 0)
  3488.         {
  3489.             // allow prompting for tabs in Firefox 3.5
  3490.             var values = { ignorable: true, preselect: this.mPref_preselect_previous_session };
  3491.             
  3492.             // Order preference:
  3493.             // 1. Temporary backup session
  3494.             // 2. Prompt or selected session
  3495.             // 3. Command line session.
  3496.             var session = (this.mPref_restore_temporary)?this.mBackupSessionName:((this.mPref_startup == 1)?this.selectSession(this._string("resume_session"), this._string("resume_session_ok"), values):
  3497.                           ((this.mPref_startup == 2)?this.mPref_resume_session:first_temp_restore));
  3498.             // If no session chosen to restore, use the command line specified session
  3499.             if (!session) session = first_temp_restore;
  3500.             if (session && (session == first_temp_restore)) {
  3501.                 this.log("recoverSession: Restoring startup command line session \"" + first_temp_restore + "\"", "DATA");
  3502.                 // Go to next command line item if it exists
  3503.                 temp_restore_index++;
  3504.             }
  3505.             this.log("recoverSession: Startup session = " + session, "DATA");
  3506.             if ((session) && (file = this.getSessionDir(session)) && file.exists())
  3507.             {
  3508.                 this.load(session, "startup", values.choseTabs);
  3509.             }
  3510.             // if user set to resume previous session, don't clear this so that way user can choose whether to backup
  3511.             // current session or not and still have it restore.
  3512.             else if ((this.mPref_startup == 2) && (this.mPref_resume_session != this.mBackupSessionName)) {
  3513.                 this.setPref("resume_session",this.mBackupSessionName);
  3514.                 this.setPref("startup",0);
  3515.             }
  3516.             if (values.ignore)
  3517.             {
  3518.                 this.setPref("resume_session", session || this.mBackupSessionName);
  3519.                 this.setPref("startup", (session)?2:0);
  3520.             }
  3521.             // Display Home Page if user selected to do so
  3522.             //if (display home page && this.isCmdLineEmpty()) {
  3523.             //    BrowserHome();
  3524.             //}
  3525.         }
  3526.         // handle browser reload with same session and when opening new windows
  3527.         else if (recoverOnly) {
  3528.             this.checkTimer();
  3529.         }
  3530.         
  3531.         // Not shutdown 
  3532.         Application.storage.set(this.mAlreadyShutdown, false);
  3533.         
  3534.         // Restore command line specified session(s) in a new window if they haven't been restored already
  3535.         if (first_temp_restore) {
  3536.             // For each remaining session in the command line
  3537.             while (temp_restore.length > temp_restore_index) {
  3538.                 file = this.getSessionDir(temp_restore[temp_restore_index]);
  3539.                 this.log(file.path);
  3540.                 if (file && file.exists()) {
  3541.                     this.log("recoverSession: Restoring additional command line session " + temp_restore_index + " \"" + temp_restore[temp_restore_index] + "\"", "DATA");
  3542.                     // Only restore into existing window if not startup and first session in command line
  3543.                     this.load(temp_restore[temp_restore_index], (((temp_restore_index > 1) || (temp_restore[0] == "0")) ? "newwindow_always" : "overwrite_window"));
  3544.                 }
  3545.                 temp_restore_index++;
  3546.             }
  3547.         }
  3548.         
  3549.         // If need to encrypt backup file, do it
  3550.         var backupFile = this.getPref("_encrypt_file");
  3551.         if (backupFile) {
  3552.             this.delPref("_encrypt_file");
  3553.             var file = this.getSessionDir(backupFile);
  3554.             var state = this.readSessionFile(file);
  3555.             if (state) 
  3556.             {
  3557.                 if (this.mSessionRegExp.test(state))
  3558.                 {
  3559.                     state = state.split("\n")
  3560.                     state[4] = this.decryptEncryptByPreference(state[4]);
  3561.                     // if could be encrypted or encryption failed but user allows unencrypted sessions
  3562.                     if (state[4]) {
  3563.                         // if encrypted save it
  3564.                         if (state[4].indexOf(":") == -1) {
  3565.                             state = state.join("\n");
  3566.                             this.writeFile(file, state);
  3567.                         }
  3568.                     }
  3569.                     // couldn't encrypt and user does not want unencrypted files so delete it
  3570.                     else this.delFile(file);
  3571.                 }
  3572.                 else this.delFile(file);
  3573.             }
  3574.         }
  3575.     },
  3576.  
  3577.     isCmdLineEmpty: function()
  3578.     {
  3579.         if (this.mApplication.name.toUpperCase() != "SEAMONKEY") {
  3580.             try {
  3581.                 // Use the defaultArgs, unless SessionStore was trying to resume or handle a crash.
  3582.                 // This handles the case where the browser updated and SessionStore thought it was supposed to display the update page, so make sure we don't overwrite it.
  3583.                 var defaultArgs = (this.mSessionStartup.sessionType != Components.interfaces.nsISessionStartup.NO_SESSION) ? 
  3584.                                   Components.classes["@mozilla.org/browser/clh;1"].getService(Components.interfaces.nsIBrowserHandler).startPage :
  3585.                                   Components.classes["@mozilla.org/browser/clh;1"].getService(Components.interfaces.nsIBrowserHandler).defaultArgs;
  3586.                 if (window.arguments && window.arguments[0] && window.arguments[0] == defaultArgs) {
  3587.                     window.arguments[0] = null;
  3588.                 }
  3589.                 return !window.arguments || !window.arguments[0];
  3590.             }
  3591.             catch(ex) {
  3592.                 this.logError(ex);
  3593.                 return false;
  3594.             }
  3595.         }
  3596.         else {
  3597.             var startPage = "about:blank";
  3598.             if (this.getPref("browser.startup.page", 1, true) == 1) {
  3599.                 startPage = this.SeaMonkey_getHomePageGroup();
  3600.             }
  3601.             return "arguments" in window && window.arguments.length && (window.arguments[0] == startPage);
  3602.         }
  3603.     },
  3604.  
  3605.     SeaMonkey_getHomePageGroup: function()
  3606.     {
  3607.         var homePage = this.mPrefRoot.getComplexValue("browser.startup.homepage", Components.interfaces.nsIPrefLocalizedString).data;
  3608.         var count = this.getPref("browser.startup.homepage.count", 0, true);
  3609.  
  3610.         for (var i = 1; i < count; ++i) {
  3611.             homePage += '\n' + this.getPref("browser.startup.homepage." + i, "", true);
  3612.         }
  3613.         return homePage;
  3614.     },
  3615.     
  3616.     // Return private browsing mode (PBM) state - If user choose to allow saving in PBM and encryption
  3617.     // is enabled, return false.
  3618.     isPrivateBrowserMode: function()
  3619.     {
  3620.         // Private Browsing Mode is only available in Firefox 3.5 and above
  3621.         if (this.mPrivateBrowsing) {
  3622.             if (this.mPref_enable_saving_in_private_browsing_mode && this.mPref_encrypt_sessions) {
  3623.                 return false;
  3624.             }
  3625.             else {
  3626.                 return this.mPrivateBrowsing.privateBrowsingEnabled;
  3627.             }
  3628.         }
  3629.         else {
  3630.             return false;
  3631.         }
  3632.     },
  3633.  
  3634.     isAutoStartPrivateBrowserMode: function()
  3635.     {
  3636.         // Private Browsing Mode is only available in Firefox 3.5 and above
  3637.         if (this.mPrivateBrowsing) {
  3638.             return this.mPrivateBrowsing.autoStarted;
  3639.         }
  3640.         else {
  3641.             return false;
  3642.         }
  3643.     },
  3644.  
  3645.     updateToolbarButton: function(aEnable)
  3646.     {
  3647.         var button = (document)?document.getElementById("sessionmanager-undo"):null;
  3648.         if (button)
  3649.         {
  3650.             var tabcount = 0;
  3651.             var wincount = 0;
  3652.             try {
  3653.                 wincount = this.mUseSSClosedWindowList ? this.mSessionStore.getClosedWindowCount() : this.getClosedWindowsCount();
  3654.                 tabcount = this.mSessionStore.getClosedTabCount(window);
  3655.             } catch (ex) { this.logError(ex); }
  3656.             this.setDisabled(button, (aEnable != undefined)?!aEnable:tabcount == 0 && wincount == 0);
  3657.         }
  3658.     },
  3659.     
  3660.     showHideToolsMenu: function()
  3661.     {
  3662.         var sessionMenu = document.getElementById("sessionmanager-menu");
  3663.         if (sessionMenu) sessionMenu.hidden = this.mPref_hide_tools_menu;
  3664.     },
  3665.  
  3666.     checkTimer: function()
  3667.     {
  3668.         // only act if timer already started
  3669.         if (this._timer && ((this.mPref__autosave_time <= 0) || !this.mPref__autosave_name)) {
  3670.             this._timer.cancel();
  3671.             this._timer = null;
  3672.             this.log("checkTimer: Session Timer stopped", "INFO");
  3673.         }
  3674.         else if (!this._timer && (this.mPref__autosave_time > 0) && this.mPref__autosave_name) {
  3675.             this.log("checkTimer: Check if session timer already running and if not start it", "INFO");
  3676.             var allWindows = this.getBrowserWindows();
  3677.             var timerRunning = false;
  3678.             for (var i in allWindows) {
  3679.                 if (allWindows[i].gSessionManager._timer) {
  3680.                     timerRunning = true;
  3681.                     break;
  3682.                 }
  3683.             }
  3684.             if (!timerRunning) {
  3685.                 this._timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  3686.                 this._timer.init(gSessionManager, this.mPref__autosave_time * 60000, Components.interfaces.nsITimer.TYPE_REPEATING_PRECISE);
  3687.                 this.log("checkTimer: Session Timer started for " + this.mPref__autosave_time + " minutes", "INFO");
  3688.             }
  3689.         }
  3690.     },
  3691.     
  3692.     checkWinTimer: function()
  3693.     {
  3694.         // only act if timer already started
  3695.         if ((this._win_timer && ((this.__window_session_time <=0) || !this.__window_session_name))) {
  3696.             this._win_timer.cancel();
  3697.             this._win_timer = null;
  3698.             this.log("checkWinTimer: Window Timer stopped", "INFO");
  3699.         }
  3700.         else if (!this._win_timer && (this.__window_session_time > 0) && this.__window_session_name) {
  3701.             this._win_timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  3702.             this._win_timer.init(gSessionManager, this.__window_session_time * 60000, Components.interfaces.nsITimer.TYPE_REPEATING_PRECISE);
  3703.             this.log("checkWinTimer: Window Timer started for " + this.__window_session_time + " minutes", "INFO");
  3704.         }
  3705.     },
  3706.     
  3707. /* ........ Auxiliary Functions .............. */
  3708.     // Undo closed tab function for SeaMonkey
  3709.     undoCloseTabSM: function(aIndex)
  3710.     {
  3711.         if (gSessionManager.mSessionStore.getClosedTabCount(window) == 0)    return;
  3712.         gSessionManager.mSessionStore.undoCloseTab(window, aIndex || 0);
  3713.         // Only need to check for empty close tab list if possibly re-opening last closed tabs
  3714.         if (!aIndex) gSessionManager.updateToolbarButton();
  3715.     },
  3716.     
  3717.     getNoUndoData: function(aLoad, aMode)
  3718.     {
  3719.         return aLoad ? { tabs: (!this.mPref_save_closed_tabs || (this.mPref_save_closed_tabs == 1 && (aMode != "startup"))),
  3720.                          windows: (!this.mPref_save_closed_windows || (this.mPref_save_closed_windows == 1 && (aMode != "startup"))) }
  3721.                      : { tabs: (this.mPref_save_closed_tabs < 2), windows: (this.mPref_save_closed_windows < 2) };
  3722.     },
  3723.  
  3724.     // count windows and tabs
  3725.     getCount: function(aState)
  3726.     {
  3727.         var windows = 0, tabs = 0;
  3728.         
  3729.         try {
  3730.             var state = this.JSON_decode(aState);
  3731.             state.windows.forEach(function(aWindow) {
  3732.                 windows = windows + 1;
  3733.                 tabs = tabs + aWindow.tabs.length;
  3734.             });
  3735.         }
  3736.         catch (ex) { this.logError(ex); };
  3737.  
  3738.         return { windows: windows, tabs: tabs };
  3739.     },
  3740.     
  3741.     getSessionState: function(aName, aOneWindow, aNoUndoData, aAutoSave, aGroup, aDoNotEncrypt, aAutoSaveTime, aState, aMerge)
  3742.     {
  3743.         // If a state is passed in either use it if aMerge is not set or merge it with the current state if aMerge is set.
  3744.         // The passed in state is used for saving old state when shutting down in private browsing mode and when appending to sessions.
  3745.         if (aState) this.log("getSessionState: " + (aMerge ? "Merging" : "Returning") + " passed in state", "INFO");
  3746.         try {
  3747.             var state = (!aMerge && aState) ? aState : (aOneWindow)?this.mSessionStore.getWindowState(window):this.mSessionStore.getBrowserState();
  3748.             
  3749.             if (aMerge && aState) {
  3750.                 state = this.JSON_decode(state);
  3751.                 aState = this.JSON_decode(aState);
  3752.                 state.windows = state.windows.concat(aState.windows);
  3753.                 if (state._closedWindows && aState._closedWindows) state._closedWindows = state._closedWindows.concat(aState._closedWindows);
  3754.                 state = this.JSON_encode(state);
  3755.             }
  3756.         }
  3757.         catch(ex) {
  3758.             // Log and rethrow errors
  3759.             this.logError(ex);
  3760.             throw(ex);
  3761.         }
  3762.         
  3763.         state = this.modifySessionData(state, aNoUndoData, true);
  3764.         var count = this.getCount(state);
  3765.         
  3766.         // encrypt state if encryption preference set and flag not set
  3767.         if (!aDoNotEncrypt) {
  3768.             state = this.decryptEncryptByPreference(state); 
  3769.             if (!state) return null;
  3770.         }
  3771.         
  3772.         return (aName != null)?this.nameState("timestamp=" + Date.now() + "\nautosave=" + ((aAutoSave)?aOneWindow?("window/" + aAutoSaveTime):("session/" + aAutoSaveTime):"false") +
  3773.                                               "\tcount=" + count.windows + "/" + count.tabs + (aGroup? ("\tgroup=" + aGroup.replace(/\t/g, " ")) : "") +
  3774.                                               "\tscreensize=" + (this._screen_width || screen.width) + "x" + (this._screen_height || screen.height) + "\n" + state, aName || "") : state;
  3775.     },
  3776.  
  3777.     restoreSession: function(aWindow, aState, aReplaceTabs, aNoUndoData, aEntireSession, aOneWindow, aStartup, aWindowSessionValues, xDelta, yDelta)
  3778.     {
  3779.         this.log("restoreSession: aWindow = " + aWindow + ", aReplaceTabs = " + aReplaceTabs + ", aNoUndoData = " + (aNoUndoData ? this.mNativeJSON.encode(aNoUndoData) : "undefined") + 
  3780.                  ", aEntireSession = " + aEntireSession + ", aOneWindow = " + aOneWindow + ", aStartup = " + aStartup + 
  3781.                  ", aWindowSessionValues = " + (aWindowSessionValues ? ("\"" + aWindowSessionValues.split("\n").join(", ") + "\"") : "undefined") + ", xDelta = " + xDelta + ", yDelta = " + yDelta, "DATA");
  3782.         // decrypt state if encrypted
  3783.         aState = this.decrypt(aState);
  3784.         if (!aState) return false;
  3785.         
  3786.         if (!aWindow)
  3787.         {
  3788.             aWindow = this.openWindow(this.getPref("browser.chromeURL", null, true), "chrome,all,dialog=no");
  3789.             aWindow.__SM_restore = function() {
  3790.                 this.removeEventListener("load", this.__SM_restore, true);
  3791.                 this.gSessionManager.restoreSession(this, aState, aReplaceTabs, aNoUndoData, null, null, null, aWindowSessionValues, xDelta, yDelta);
  3792.                 delete this.__SM_restore;
  3793.             };
  3794.             aWindow.addEventListener("load", aWindow.__SM_restore, true);
  3795.             return true;
  3796.         }
  3797.  
  3798.         aState = this.modifySessionData(aState, aNoUndoData, false, aEntireSession, aStartup, xDelta, yDelta);  
  3799.  
  3800.         if (aEntireSession)
  3801.         {
  3802.             this.mSessionStore.setBrowserState(aState);
  3803.         }
  3804.         else
  3805.         {
  3806.             if (aOneWindow) aState = this.makeOneWindow(aState);
  3807.             this.mSessionStore.setWindowState(aWindow, aState, aReplaceTabs || false);
  3808.         }
  3809.         
  3810.         // Store autosave values into window value and also into window variables
  3811.         if (!this.__window_session_name) this.getAutoSaveValues(aWindowSessionValues, true);
  3812.         this.log("restoreSession: restore done, window_name  = " + this.__window_session_name, "DATA");
  3813.         return true;
  3814.     },
  3815.  
  3816.     nameState: function(aState, aName)
  3817.     {
  3818.         if (!/^\[SessionManager v2\]/m.test(aState))
  3819.         {
  3820.             return "[SessionManager v2]\nname=" + aName.replace(/\t/g, " ") + "\n" + aState;
  3821.         }
  3822.         return aState.replace(/^(\[SessionManager v2\])(?:\nname=.*)?/m, function($0, $1) { return $1 + "\nname=" + aName.replace(/\t/g, " "); });
  3823.     },
  3824.     
  3825.     makeOneWindow: function(aState)
  3826.     {
  3827.         aState = this.JSON_decode(aState);
  3828.         if (aState.windows.length > 1)
  3829.         {
  3830.             // take off first window
  3831.             var firstWindow = aState.windows.shift();
  3832.             // make sure toolbars are not hidden on the window
  3833.             delete(firstWindow.hidden);
  3834.             // Move tabs to first window
  3835.             aState.windows.forEach(function(aWindow) {
  3836.                 while (aWindow.tabs.length > 0)
  3837.                 {
  3838.                     this.tabs.push(aWindow.tabs.shift());
  3839.                 }
  3840.             }, firstWindow);
  3841.             // Remove all but first window
  3842.             aState.windows = [];
  3843.             aState.windows[0] = firstWindow;
  3844.         }
  3845.         return this.JSON_encode(aState);
  3846.     },
  3847.     
  3848.     modifySessionData: function(aState, aNoUndoData, aSaving, aReplacingWindow, aStartup, xDelta, yDelta)
  3849.     {
  3850.         if (!xDelta) xDelta = 1;
  3851.         if (!yDelta) yDelta = 1;
  3852.     
  3853.         // Don't do anything if not modifying session data
  3854.         if (!(aNoUndoData || aSaving || aReplacingWindow || aStartup || (xDelta != 1) || (yDelta != 1))) {
  3855.             return aState;
  3856.         }
  3857.         aState = this.JSON_decode(aState);
  3858.         
  3859.         // set _firsttabs to true on startup to prevent closed tabs list from clearing when not overwriting tabs.
  3860.         if (aStartup) aState._firstTabs = true;
  3861.         
  3862.         var fixWindow = function(aWindow) {
  3863.             // Strip out cookies if user doesn't want to save them
  3864.             if (aSaving && !this.mPref_save_cookies) delete(aWindow.cookies);
  3865.  
  3866.             // remove closed tabs            
  3867.             if (aNoUndoData && aNoUndoData.tabs) aWindow._closedTabs = [];
  3868.             
  3869.             // adjust window position and height if screen dimensions don't match saved screen dimensions
  3870.             aWindow.width = aWindow.width * xDelta;
  3871.             aWindow.height = aWindow.height * yDelta;
  3872.             aWindow.screenX = aWindow.screenX * xDelta;
  3873.             aWindow.screenY = aWindow.screenY * yDelta;
  3874.         };
  3875.         
  3876.         // process opened windows
  3877.         aState.windows.forEach(fixWindow, this);
  3878.         
  3879.         // process closed windows (for sessions only)
  3880.         if (aState._closedWindows) {
  3881.             if (this.mUseSSClosedWindowList && aNoUndoData && aNoUndoData.windows) {
  3882.                 aState._closedWindows = [];
  3883.             }
  3884.             else  {
  3885.                 aState._closedWindows.forEach(fixWindow, this);
  3886.             }
  3887.         }
  3888.  
  3889.         // if only one window, don't allow toolbars to be hidden
  3890.         if (aReplacingWindow && (aState.windows.length == 1) && aState.windows[0].hidden) {
  3891.             delete (aState.windows[0].hidden);
  3892.         }
  3893.         return this.JSON_encode(aState);
  3894.     },
  3895.  
  3896.     getFormattedName: function(aTitle, aDate, aFormat)
  3897.     {
  3898.         function cut(aString, aLength)
  3899.         {
  3900.             return aString.replace(new RegExp("^(.{" + (aLength - 3) + "}).{4,}$"), "$1...");
  3901.         }
  3902.         function toISO8601(aDate, format)
  3903.         {
  3904.             if (format) {
  3905.                 return aDate.toLocaleFormat(format);
  3906.             }
  3907.             else {
  3908.                 return [aDate.getFullYear(), pad2(aDate.getMonth() + 1), pad2(aDate.getDate())].join("-");
  3909.             }
  3910.         }
  3911.         function pad2(a) { return (a < 10)?"0" + a:a; }
  3912.         
  3913.         return (aFormat || this.mPref_name_format).split("%%").map(function(aPiece) {
  3914.             return aPiece.replace(/%(\d*)([tdm])(\"(.*)\")?/g, function($0, $1, $2, $3, $4) {
  3915.                 $0 = ($2 == "t")?aTitle:($2 == "d")?toISO8601(aDate, $4):pad2(aDate.getHours()) + ":" + pad2(aDate.getMinutes());
  3916.                 return ($1)?cut($0, Math.max(parseInt($1), 3)):$0;
  3917.             });
  3918.         }).join("%");
  3919.     },
  3920.  
  3921.     makeFileName: function(aString)
  3922.     {
  3923.         aString = aString.replace(/\t/g, " ");
  3924.         return aString.replace(/[^\w ',;!()@&*+=~\x80-\xFE-]/g, "_").substr(0, 64) + this.mSessionExt;
  3925.     },
  3926.     
  3927.     // Look for open window sessions
  3928.     getWindowSessions: function()
  3929.     {
  3930.         return this.mApplication.storage.get(this.mActiveWindowSessions, {});
  3931.     },
  3932.  
  3933.     getBrowserWindows: function()
  3934.     {
  3935.         var windowsEnum = this.mWindowMediator.getEnumerator("navigator:browser");
  3936.         var windows = [];
  3937.         
  3938.         while (windowsEnum.hasMoreElements())
  3939.         {
  3940.             windows.push(windowsEnum.getNext());
  3941.         }
  3942.         
  3943.         return windows;
  3944.     },
  3945.     
  3946.     updateAutoSaveSessions: function(aOldName, aNewName) 
  3947.     {
  3948.         var updateTitlebar = false;
  3949.         
  3950.         // auto-save session
  3951.         if (this.mPref__autosave_name == aOldName) 
  3952.         {
  3953.             this.log("updateAutoSaveSessions: autosave change: aOldName = " + aOldName + ", aNewName = " + aNewName, "DATA");
  3954.             // rename or delete?
  3955.             if (aNewName) {
  3956.                 this.setPref("_autosave_values", this.mergeAutoSaveValues(aNewName, this.mPref__autosave_group, this.mPref__autosave_time));
  3957.             }
  3958.             else {
  3959.                 this.setPref("_autosave_values","");
  3960.             }
  3961.             updateTitlebar = true;
  3962.         }
  3963.         
  3964.         // window sessions
  3965.         this.getBrowserWindows().forEach(function(aWindow) {
  3966.             if (aWindow.gSessionManager && aWindow.gSessionManager.__window_session_name && (aWindow.gSessionManager.__window_session_name == aOldName)) { 
  3967.                 this.log("updateAutoSaveSessions: window change: aOldName = " + aOldName + ", aNewName = " + aNewName, "DATA");
  3968.                 aWindow.gSessionManager.__window_session_name = aNewName;
  3969.                 // delete
  3970.                 if (!aNewName)
  3971.                 {
  3972.                     aWindow.gSessionManager.__window_session_group = null;
  3973.                     aWindow.gSessionManager.__window_session_time = 0;
  3974.                 }
  3975.                 updateTitlebar = true;
  3976.             }
  3977.         }, this);
  3978.         
  3979.         // Update titlebars
  3980.         if (updateTitlebar) this.mObserverService.notifyObservers(null, "sessionmanager:updatetitlebar", null);
  3981.     },
  3982.  
  3983.     doResumeCurrent: function()
  3984.     {
  3985.         return (this.getPref("browser.startup.page", 1, true) == 3)?true:false;
  3986.     },
  3987.  
  3988.     isCleanBrowser: function(aBrowser)
  3989.     {
  3990.         return aBrowser.sessionHistory.count < 2 && aBrowser.currentURI.spec == "about:blank";
  3991.     },
  3992.  
  3993.     setDisabled: function(aObj, aValue)
  3994.     {
  3995.         if (aValue)
  3996.         {
  3997.             aObj.setAttribute("disabled", "true");
  3998.         }
  3999.         else
  4000.         {
  4001.             aObj.removeAttribute("disabled");
  4002.         }
  4003.     },
  4004.  
  4005.     getEOL: function()
  4006.     {
  4007.         return /win|os[\/_]?2/i.test(navigator.platform)?"\r\n":/mac/i.test(navigator.platform)?"\r":"\n";
  4008.     },
  4009.  
  4010.     _string: function(aName)
  4011.     {
  4012.         return this.mBundle.getString(aName);
  4013.     },
  4014.  
  4015.     // Decode JSON string to javascript object - use JSON if built-in.
  4016.     JSON_decode: function(aStr, noError) {
  4017.         var jsObject = { windows: [{ tabs: [{ entries:[] }], selected:1, _closedTabs:[] }], _JSON_decode_failed:true };
  4018.         try {
  4019.             var hasParens = ((aStr[0] == '(') && aStr[aStr.length-1] == ')');
  4020.         
  4021.             // JSON can't parse when string is wrapped in parenthesis
  4022.             if (hasParens) {
  4023.                 aStr = aStr.substring(1, aStr.length - 1);
  4024.             }
  4025.         
  4026.             // Session Manager 0.6.3.5 and older had been saving non-JSON compiant data so try to use evalInSandbox if JSON parse fails
  4027.             try {
  4028.                 jsObject = this.mNativeJSON.decode(aStr);
  4029.             }
  4030.             catch (ex) {
  4031.                 if (/[\u2028\u2029]/.test(aStr)) {
  4032.                     aStr = aStr.replace(/[\u2028\u2029]/g, function($0) {"\\u" + $0.charCodeAt(0).toString(16)});
  4033.                 }
  4034.                 jsObject = this.mComponents.utils.evalInSandbox("(" + aStr + ")", new this.mComponents.utils.Sandbox("about:blank"));
  4035.             }
  4036.         }
  4037.         catch(ex) {
  4038.             jsObject._JSON_decode_error = ex;
  4039.             if (!noError) this.sessionError(ex);
  4040.         }
  4041.         return jsObject;
  4042.     },
  4043.     
  4044.     // Encode javascript object to JSON string - use JSON if built-in.
  4045.     JSON_encode: function(aObj) {
  4046.         var jsString = null;
  4047.         try {
  4048.             jsString = this.mNativeJSON.encode(aObj);
  4049.             // Needed until Firefox bug 387859 is fixed or else Firefox won't except JSON strings with \u2028 or \u2029 characters
  4050.             if (/[\u2028\u2029]/.test(jsString)) {
  4051.                 jsString = jsString.replace(/[\u2028\u2029]/g, function($0) {"\\u" + $0.charCodeAt(0).toString(16)});
  4052.             }
  4053.         }
  4054.         catch(ex) {
  4055.             this.sessionError(ex);
  4056.         }
  4057.         return jsString;
  4058.     },
  4059.     
  4060.     // 
  4061.     // Logging functions
  4062.     // Get logger singleton (this will create it if it does not exist)
  4063.     //
  4064.     log: function(aMessage, aLevel, aForce) {
  4065.         if (this.logger()) this.logger().log(aMessage, aLevel, aForce);
  4066.     },
  4067.  
  4068.     logError: function(aMessage, aForce) {
  4069.         if (this.logger()) this.logger().logError(aMessage, aForce);
  4070.     },
  4071.     
  4072.     deleteLogFile: function(aForce) {
  4073.         if (this.logger()) this.logger().deleteLogFile(aForce);
  4074.     },
  4075.  
  4076.     openLogFile: function() {
  4077.         if (this.logger()) this.logger().openLogFile(this._string("file_not_found"));
  4078.     }
  4079. };
  4080.  
  4081. // String.trim is not defined in Firefox 3.0, so define it here if it isn't already defined.
  4082. if (typeof(String.trim) != "function") {
  4083.     String.prototype.trim = function() {
  4084.         return this.replace(/^\s+|\s+$/g, "");
  4085.     };
  4086. }
  4087.  
  4088. // Initialize conditional variables, if no Session Store don't add event listener
  4089. if (gSessionManager.initialize()) {
  4090.     window.addEventListener("load", gSessionManager.onLoad_proxy, false);
  4091. }